Upgrade to Pro — share decks privately, control downloads, hide ads and more …

PHPerKaigi 2022をきっかけにPHPStanにコントリビュートした話 / PHPe...

PHPerKaigi 2022をきっかけにPHPStanにコントリビュートした話 / PHPerKaigi makes me PHPStan's Contributor

muno92

May 27, 2022
Tweet

More Decks by muno92

Other Decks in Programming

Transcript

  1. こんなルールを作りました <?php $dt = new DateTimeImmutable(); $dt->__construct('2022/1/23'); ↓ > ./vendor/bin/phpstan

    analyse -c phpstan.neon ------ ------------------------------------------------------------- Line sample.php ------ ------------------------------------------------------------- 4 Call to __construct() on an existing object is not allowed. ------ ------------------------------------------------------------- ※ デフォルトでは有効になっていないため、次ページ記載の設定が必要 3
  2. 良い感じのルールクラス <?php declare(strict_types = 1); class MyRule implements Rule {

    public function getNodeType(): string { // 一致するコードがprocessNode でチェックされる return Node\Expr\ メソッド呼び出しを行っているパーツ::class; } public function processNode(Node $node, Scope $scope): array { // __construct 実行をチェック if ( エラーでない場合) { return []; } return [' エラーです']; } } 14
  3. PHPerKaigi (day3?) ハッカソン爆誕 加速するDiscord 名前どうする? 「1. ソースコード内でメソッド呼び出しを行っている箇所を解析し」   どのノードタイプで引っ掛ける? 「3. 正常な使い方をしていない場合」  

    弾いてはいけない正常なパターンはどれ? どこに実装する? PHPStan本体 phpstan-strict-rules こんな感じのコードでいけそう (from mpywさん、yu-ichiroさん) 16
  4. これはOK class MyDateTimeImmutable extends DateTimeImmutable { public function __construct(string $datetime

    = "now", ?\DateTimeZone $timezone = null)) { parent::__construct($datetime, $timezone); } } 17
  5. これはNG class MyDateTimeImmutable extends DateTimeImmutable { public function mutate(string $datetime

    = "now", ?\DateTimeZone $timezone = null)) { parent::__construct($datetime, $timezone); } } $dt = new DateTimeImmutable(); $dt->__construct('2022/1/23'); 18
  6. こういったパターンもある (OK?) from mpywさん class MyDateTimeImmutable extends DateTimeImmutable { public

    function __construct(string $datetime = "now", ?\DateTimeZone $timezone = null)) { self::__construct($datetime, $timezone); } } ※ 許容する形で実装したが、最終的にマージされたコードではNGに 19
  7. 実装(PHPStan作者の方による調整が入った最終形) <?php declare(strict_types = 1); namespace PHPStan\Rules\Methods; // use は行数の都合で省略

    class IllegalConstructorMethodCallRule implements Rule { public function getNodeType(): string { // 戻り値のNodeType (今回で言えばメソッド呼び出し)が解析対象のコードに含まれていた場合、processNode で解析する return Node\Expr\MethodCall::class; } public function processNode(Node $node, Scope $scope): array { if (!$node->name instanceof Node\Identifier || $node->name->toLowerString() !== '__construct') { // エラー無し return []; } return [ RuleErrorBuilder::message('Call to __construct() on an existing object is not allowed.') ->build(), ]; } } どうやってここに行き着く? 21
  8. とりあえずvar_dump 3ページ目のコードを解析した場合のvar_dump($node)を抜粋 object(PhpParser\Node\Expr\MethodCall)#3146 (4) { ["attributes":protected]=> array(9) {} // 4

    行目にあるdt という名前の変数がメソッドを呼び出している ["var"]=> object(PhpParser\Node\Expr\Variable)#3142 (2) { ["attributes":protected]=> array(9) { ["startLine"]=> int(4) } ["name"]=> string(2) "dt" } // 呼び出しているメソッドの名前は__construct ["name"]=> object(PhpParser\Node\Identifier)#3143 (2) { ["attributes":protected]=> array(6) {} ["name"]=> string(11) "__construct" } // 今回は引数の情報は要らない ["args"]=> array(1) {} } 22