laravel-dataのプロパティをreadonly必須にするPHPStanのカスタムルール
その際プロパティを定義するがプロパティの中身を変更して欲しくないので可能な限りreadonlyにしたい
人力で検出するのは忘れてしまうので,PHPStanのカスタムルールとして実装した code:php
<?php
declare(strict_types=1);
use PhpParser\Node;
use PhpParser\Node\Stmt\Class_;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\IdentifierRuleError;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
/**
* @implements Rule<Class_>
*/
class ReadonlyDataClassRule implements Rule
{
private const IDENTIFIER = 'custom.ReadonlyDataClass';
private const DATA_CLASS = 'Spatie\LaravelData\Data';
private const MESSAGE = 'The property of the class that extends the Data class must be readonly.';
public function getNodeType(): string
{
return Class_::class;
}
public function processNode(Node $node, Scope $scope): array
{
if ($node->extends?->toString() !== self::DATA_CLASS) {
return [];
}
return [
...$this->checkProperties($node),
...$this->checkConstructorPromotion($node),
];
}
/**
* @return list<IdentifierRuleError>
*/
private function checkProperties(Class_ $node): array
{
$errors = [];
foreach ($node->getProperties() as $property) {
if ($property->isReadonly()) {
continue;
}
$errors[] = RuleErrorBuilder::message(self::MESSAGE)
->identifier(self::IDENTIFIER)
->line($property->getEndLine())
->build();
}
return $errors;
}
/**
* @return list<IdentifierRuleError>
*/
private function checkConstructorPromotion(Class_ $node): array
{
$constructor = $node->getMethod('__construct');
if ($constructor === null) {
return [];
}
$errors = [];
foreach ($constructor->params as $param) {
if ($param->isReadonly()) {
continue;
}
$errors[] = RuleErrorBuilder::message(self::MESSAGE)
->identifier(self::IDENTIFIER)
->line($param->getEndLine())
->build();
}
return $errors;
}
}