【確認した日付を入れる】、配布用のzipを別の用事で展開して、中のPHPを静的解析に掛けました。108ファイル全部が、直接アクセスの保護なしと判定されました。
リポジトリには書いてあります。全ファイルの先頭に、いつもの一行が入っています。
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
ソースを検査すれば通ります。手元のCIも通っていました。通らないのは、利用者がインストールするほうだけでした。
検証環境:【使っているビルドツールとバージョン、確認日を入れる】
ビルドが2つのものを書き換えていた
原因は、ビルド工程がPHPを再出力していたことでした。グローバル関数を完全修飾に直す処理が入っていて、先ほどの一行がこうなります。
if (!\defined('ABSPATH')) {
exit;
}
動作は同じです。人が読んでも同じです。ただ、検査する側がこの形を保護とみなしませんでした。空白の有無も、先頭のバックスラッシュも、検査のパターンに合っていない。
同じ再出力が、もう一つ壊していました。コード規約の抑制注釈です。
抑制注釈は、書いた行の位置に意味があります。次の行に効くもの、その行に効くもの。再出力でコードの整形が変わると、注釈だけが元の位置に残って、守るはずだった行から1行ずれる。171件が全部ずれていました。
こちらはもっと厄介です。判定が「保護なし」に変わるのではなく、抑制が効かなくなって関係のない警告が出たり、逆に意図しない行の警告を消したりします。気づく手がかりが少ない。
なぜソース検査では見つからないのか
当たり前のことなのですが、書いたときには確認しているからです。
先頭のガードを書いた。静的解析を掛けた。通った。この時点でわたしは「確認した」と言えます。言えるのですが、確認した対象はリポジトリのファイルです。利用者が受け取るのはビルド後のファイルで、その2つが同じである保証はどこにもありません。
普段は同じです。ビルドがコードの意味を変えることはないので、意味の上では正しい。壊れたのは意味ではなく、検査する側が見ている表面のほうでした。
WordPress プラグインの場合、審査は配布パッケージに対して行われます。手元で通したものと審査に出すものが違うなら、手元の結果には何の意味もありません。
配布物に検査を掛ける
やることは単純です。zipを作って、展開して、そこに解析を掛ける。順番を変えるだけです。
# ビルド → 配布物を展開 → その中身を検査する
npm run build
rm -rf /tmp/dist-check && mkdir -p /tmp/dist-check
unzip -q dist/my-plugin.zip -d /tmp/dist-check
vendor/bin/phpcs --standard=phpcs.xml /tmp/dist-check/my-plugin
CIに入れるときは、ソースへの検査を消さずに、配布物への検査を足す形にしました。両方あると、差分が出たときに原因の切り分けが早くなります。ソースだけ落ちたなら書いたコードの問題、配布物だけ落ちたならビルドの問題です。
Plugin Check を使っているなら、そちらも配布物に対して走らせてください。WP-CLI 経由なら、展開先をプラグインディレクトリに置いて叩けます。
ビルド後も残っているべき性質を検査する
解析を配布物に移したあと、テストの書き方も変えました。「ソースにこう書いてある」ではなく、「ビルドを通過したあともこの性質が残っている」を見る形です。いま4つ検査しています。
直接アクセスのガードが、検査に通る形式で先頭50行以内にある
書いてあるかどうかではなく、検査に通る形式で書いてあるか。行数の上限を付けたのは、ビルドがファイル先頭にコメントブロックを差し込むことがあるためです。
抑制注釈が単独行にある
コードと同じ行に書くと、整形で行が分かれたときに位置が狂います。単独行なら、少なくとも「どの行を指しているか」が壊れにくくなります。
テンプレート内では範囲指定を使う
出力側のテンプレートは整形の影響を受けやすいので、行を指す注釈ではなく、開始と終了で囲む形にしました。
抑制注釈を重ねない
これも実際に踏みました。同じ箇所に注釈を並べて書くと、最後の1行しか届きません。複数の規則を抑制したい場合は、1行にまとめるか、範囲指定に切り替えます。
手元で確かめる順番
同じことが起きているかどうかは、5分で分かります。
配布用のパッケージを1つ作って、どこかに展開して、そこに普段使っている解析を掛けるだけです。件数がソースと一致していれば問題ありません。一致しなければ、ビルドが何かを書き換えています。
差分が出た場合は、書き換えられた1ファイルを開いて、ソースと並べて見てください。わたしの場合は先頭の一行と、注釈の位置でした。
次の自分に渡すメモ
- 静的解析はソースと配布物の両方に掛ける。片方だけなら配布物
- 検査の対象が、利用者の手元に届くものと同一かを先に確かめる
- 抑制注釈は単独行。テンプレートは範囲指定。重ねない
- ガードは「書いてあるか」ではなく「検査に通る形式か」で見る
検査したと言えるのは、検査した対象が出荷されるときだけです。
ふだんはraplsworks.comで、WordPressプラグイン開発やClaude Codeまわりのことを書いています。