kakakakakku blog

Weekly Tech Blog: Keep on Learning!

PHPCompatibility で `break 0;` と `continue 0;` を検知できるようにした話

先月に PHPCompatibility を使ったときに,PHP 5.4 から非互換になった break 0; は検知できなかったと書いた.せっかくなら検知できるようにしようと思って PHPCompatibility に PR を送ってマージしてもらった話を簡単に書いておこうと思う.

kakakakakku.hatenablog.com

PR

以下の PR を送った.

github.com

変更したケースを紹介すると,以下で言う break 0;continue 0; を検知できるようにした.

<?php
$return = 1;
break $result;
break 0;
continue $result;
continue 0;

エラーメッセージは既存と変えていて Using 0 as an argument on break or continue is forbidden since PHP 5.4 としている.

----------------------------------------------------------------------
FOUND 4 ERRORS AFFECTING 4 LINES
----------------------------------------------------------------------
 3 | ERROR | Using a variable argument on break or continue is
   |       | forbidden since PHP 5.4
 4 | ERROR | Using 0 as an argument on break or continue is forbidden
   |       | since PHP 5.4
 5 | ERROR | Using a variable argument on break or continue is
   |       | forbidden since PHP 5.4
 6 | ERROR | Using 0 as an argument on break or continue is forbidden
   |       | since PHP 5.4
----------------------------------------------------------------------

フィードバック

1回目に出した PR では,大きな変更をせずに breakcontinue の引数が 0 だった場合にエラーフラグを立てて,エラーメッセージを少し修正する形で提案してみた.

(省略)
+  else if ($tokens[$curToken]['type'] === 'T_LNUMBER' && $tokens[$curToken]['content'] === '0') {
+      $isError = true;
+      break;
+  }
(省略)
   if ($isError === true) {
-     $error = 'Using a variable argument on break or continue is forbidden since PHP 5.4';
+     $error = 'Using a variable argument or zero value on break or continue is forbidden since PHP 5.4';
      $phpcsFile->addError($error, $stackPtr);
   }
(省略)

すると「ドキュメントに書かれてる Changelog 的には異なる非互換項目だからエラーメッセージを変えよう!」とフィードバックをもらうことができて,確かになー!と思って納得した.

そこからエラータイプを定義して,エラーメッセージを変えて表示するように実装し直した.PHPUnit も最近マージされたリファクタリングによって Data Provider を使うようになっていて,メンテナンスしやすく修正できた.あと細かいところだと,エラーメッセージの英語表現に関してもフィードバックしてもらえて助かった.

トークン解析

基本的には PHP_CodeSniffer_File クラスに定義されている getTokens() を使ってトークナイズして,返ってきた結果を処理するフローで検知をする.

<?php
break 0;

例えば上記のコードをトークナイズすると,トークンごとに分割された配列が返ってくる.今回検知した 0type = T_LNUMBERcontent = "0" な要素で判定することができる.

Array
(
    [0] => Array
        (
            [type] => T_OPEN_TAG
            [code] => 379
            [content] => <?php

            [line] => 1
            [column] => 1
            [length] => 5
            [level] => 0
            [conditions] => Array
                (
                )

        )

    [1] => Array
        (
            [code] => 343
            [type] => T_BREAK
            [content] => break
            [line] => 2
            [column] => 1
            [length] => 5
            [level] => 0
            [conditions] => Array
                (
                )

        )

    [2] => Array
        (
            [code] => 382
            [type] => T_WHITESPACE
            [content] =>
            [line] => 2
            [column] => 6
            [length] => 1
            [level] => 0
            [conditions] => Array
                (
                )

        )

    [3] => Array
        (
            [code] => 317
            [type] => T_LNUMBER
            [content] => 0
            [line] => 2
            [column] => 7
            [length] => 1
            [level] => 0
            [conditions] => Array
                (
                )

        )

    [4] => Array
        (
            [type] => T_SEMICOLON
            [code] => PHPCS_T_SEMICOLON
            [content] => ;
            [line] => 2
            [column] => 8
            [length] => 1
            [level] => 0
            [conditions] => Array
                (
                )

        )

    [5] => Array
        (
            [type] => T_WHITESPACE
            [code] => 382
            [content] =>

            [line] => 2
            [column] => 9
            [length] => 0
            [level] => 0
            [conditions] => Array
                (
                )

        )

)

ハマったこと

PHPCompatibility の PHPUnit を実行したら以下のエラーが出て動かなかった.

Undefined index: quiet

以下の Issue に類似していていたため CodeSniffer/CLI.php を修正して対応した.

github.com

まとめ

簡単な機能追加ではあるけど,フィードバックをもらって実装し直したプロセスは個人的に非常に勉強になった.現在,案件で PHPCompatibility を使っていて便利なので,感謝の気持ちを込めて,今後も積極的に PR を送って行きたいと思う.