🦊

次世代 PHP 静的解析リンター `Fennec`

2024/11/04に公開
$ fennec help
--------------------------------------------------------------------------
  /\   /\            |
 //\\_//\\     ____  | Fennec 🦊 is an all-in-one, oxidized PHP toolchain,
 \_     _/    /   /  | built to handle everything from static analysis and
  / * * \    /^^^]   | refactoring to full project management.
  \_\O/_/    [   ]   |
   /   \_    [   /   |
   \     \_  /  /    |
    [ [ /  \/ _/     | https://carthage.software/fennec
   _[ [ \  /_/       |
--------------------------------------------------------------------------

Usage: fennec <COMMAND>

Commands:
  lint  Lint the project according to the `fennec.toml` configuration or default settings
  fix   Fix lint issues identified during the linting process
  help  Print this message or the help of the given subcommand(s)

Options:
  -h, --help
          Print help (see a summary with '-h')

  -V, --version
          Print version

https://github.com/carthage-software/fennec


閑話休題。まずは、インストールと実行。

インストール

cargo install --git https://github.com/carthage-software/fennec

セットアップ&実行 fennec lint

cd "実行したいプロジェクトフォルダ"
echo '[source]
paths = ["src"]' > fennec.toml
fennec lint

実行速度の比較

  • 今回はある程度のコードサイズがあるプロジェクトとして、ec-cube を例に確認してみた
  • 対象は、src でファイル数は、531

比較結果

  • php-cs-fixer fix の場合

    • ./vendor/bin/php-cs-fixer fix --dry-run src で実行したとき
        * Found 223 of 531 files that can be fixed in 8.179 seconds, 32.000 MB memory used
  • phpstan

    • time ./vendor/bin/phpstan analyse --level=5 src で実行したとき
    • ./vendor/bin/phpstan analyse --level=5 src 74.24s user 1.29s system 623% cpu 12.109 total
  • fennect lint の場合

    • time fennec lint で実行したとき
    • fennec lint 1.08s user 0.75s system 113% cpu 1.612 total

比較結果についての補足

  • PHP のリンターでの導入・設定をしたことある方ならばお気づきの通り、「rustなので速いんだろうね。」 位の参考値。
    • 並行実行の調整の余地はそれぞれである通り

Fennec についての注目点紹介

前述までのところで Fennec の直近の状況を踏まえたところで、Fennec についての説明をしたい。Fennec は、githubでの About 欄で、" The Oxidized PHP Toolchain" と記載がある通り、Rust製での静的解析/linter である。まだ、このプロジェクトは開発開始から1週間ほどだが、現状でも十分に実運用できるレベルのものが出来ていると言える。作者はpslの作者としても知られるazjezzである。

フォーマットチェック ~ 静的解析(PHPStan/psalm) の間としての選択肢としての存在

Fennec はまだ開発初期段階でもあるので位置づけを定義するのも失礼な話だが、個人的にはチェックツールの選択肢として候補に挙げられる魅力があると考えている。

現状のPHPリンターの問題点

大まかにPHPでのリンターとして、以下のツールが主に利用されているのが昨今のPHPプロジェクトでの現状としてあげられる。

  • フォーマットチェック
    • PHP_CodeSniffer, php-cs-fixer
  • 静的解析
    • psalm, PHPStan, phan

それぞれの大雑把すぎる特徴としては、

  • PHP_CodeSniffer, php-cs-fixer
    • 早い・低メモリ
    • トークンベース。ASTでのチェックができない(複雑なチェックができない)
  • psalm, PHPStan, phan
    • 重い・全体解析に時間がかかる
    • ASTベース

それぞれのツールのデフォルトのチェックについては、そのツールを使えばよいが、プロジェクト個別のルールを設定・作成しようとした場合に、フォーマットチェック系のものを利用するか静的解析ツールのものを利用するか、悩んだケースもある。
とりわけ、PHP_CodeSniffer 関連で言えば自作のSnifferを作る際にSlevomat Coding Standard をベースに使うなりsymplify/coding-standard をベースに使うなりで、めちゃくちゃ頑張ってトークンベースでのルール作成は出来ることは出来るが、Slevomat Coding Standardに関して言えば、一部のルールは PHPStanに移行されたことが示すように、ASTベースでないと辛いというところがある。
かと言って、PHPStanにて全てのチェックを行うのか?あくまで静的解析ツールでは動作エラーとなりうる所を検出すべきではないのか?規約としてのチェックはコーディング標準違反として検出すべきではないか?PHPStanにすべて寄せた場合、指摘まみれとなり他の開発者は盲目的に"Make phpstan happy" な修正や "ignore-error" であふれてしまうのではないか?そして割と実行が"重い"!。。。といった危惧もあり、リンター/静的解析でのチェック以外での第3の指摘ツールとしての存在を模索している方もいるだろう。

ASTベースでの規約チェックという立ち位置では、pmjonesによるphp-styler も存在するが、結局はPHPベースであるところや現状の成熟度合などを鑑みるに期待はできない、というのが正直なところである。

Fennecは、フォーマットチェックとPHP純正静的解析ツールの隙間の存在として導入の候補に挙げられる。例として、次項でデバッグ用関数の禁止をする場合の設定例を見てみよう

設定例 - デバッグ用関数の禁止をする場合

Fennec は現状でもlinterのプラグインとしていくつかのルールを用意しており、

ls ./crates/linter/src/plugin/
best_practices  comment  consistency  mod.rs  naming  redundancy  safety  strictness  symfony

ベストプラクティスとしてのプラグインは、以下のルールが確認できる。

tree ./crates/linter/src/plugin/best_practices
./crates/linter/src/plugin/best_practices
├── mod.rs
└── rules
    ├── disallowed_functions.rs
    ├── excessive_nesting.rs
    ├── mod.rs
    ├── no_debug_symbols.rs
    ├── no_goto.rs
    ├── no_unused_parameter.rs
    ├── use_while_instead_of_for.rs
    └── utils.rs

この中で、デバッグ用関数のチェックをしたい場合は、以下のように fennec.toml を設定する。

[source]
paths = ["src"]

[linter]
# The highest level of issues to report: "Error", "Warning", "Info", "Help", or "Off"
level = "Warning"
# Whether to report issues in external dependencies
external = false
# Whether to enable the default set of plugins
default_plugins = false

plugins = [
    "best-practices"
]

[[linter.rules]]
name = "best-practices/no-debug-symbols"
level = "Error"

例として、以下のようにデバッグ関数が紛れ込んでいる場合でも、

diff --git a/src/Eccube/Controller/CartController.php b/src/Eccube/Controller/CartController.php
index 99a1ab5739..421b7484ab 100644
--- a/src/Eccube/Controller/CartController.php
+++ b/src/Eccube/Controller/CartController.php
@@ -28,6 +28,8 @@ use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\Routing\Annotation\Route;

+use function var_dump as d;
+
 class CartController extends AbstractController
 {
     /**
@@ -81,6 +83,7 @@ class CartController extends AbstractController
         // カートを取得して明細の正規化を実行
         $Carts = $this->cartService->getCarts();
         $this->execPurchaseFlow($Carts);
+        d($Carts);

         // TODO itemHolderから取得できるように
         $least = [];
fennec lint
error: usage of debug function: `var_dump`
   ┌─ src/Eccube/Controller/CartController.php:86:9
   │
86 │         d($Carts);
   │         ^^^^^^^^^
   │
   = avoid using debug functions like `var_dump`, `print_r`, etc. in production code.
   = help: remove the debug function call.

とエイリアスのコンテキストを解釈できたうえでのチェックが行える。

現状のエラー出力でも、以下のように分かりやすいハイライト表示が行われる。

いかがでしたか?

Fennec はまだearly段階ではありますが、パーサー・lexer・AST・リフレクションを自作している非常に意欲的なプロジェクトでもあり、現段階でも前述の通りプロジェクト個別の規約のチェックにも導入候補に挙げられると思っています。ぜひ一度お試しください。

Discussion