ob-phpstanを作った

Introduction

最近phpstanを弄ることが増えたのですが、逐一playgroundを開いてWebエディタ内で編集をするのが面倒に感じていました。 org-mode内で完結をするようにしたいと考えるのはEmacsユーザーなら当然の発想です。 そこで今回は org-babel 経由でphpstanの実行結果をorg内で管理できるようにしました。

使い方

https://github.com/emacs-php/ob-phpstan

2023/04/16現在、melpaにはまだ登録をしていないので各自で落してきてパスを通してください。

(add-to-list 'load-path "/path/to/ob-phpstan.el")
(require 'ob-phpstan)

次に php-mode を派生させた phpstan-mode を作成してください。

(eval-after-load "org" '(add-to-list 'org-src-lang-modes '("phpstan" . phpstan)))

org-babel-phpstan-command に各自で phpstan へのパスを設定してください。

(with-eval-after-load 'ob-phpstan
  (setq org-babel-phpstan-command "/path/to/dir/phpstan"))

あとは適宜orgファイル内でコードブロック作成し、 org-babel を実行してください。

#+begin_src phpstan :level 0
  class HelloWorld
  {
          public function sayHello(DateTimeImutable $date): void
          {
                  echo 'Hello, ' . $date->format('j. n. Y');
          }
  }
#+end_src

#+RESULTS:
#+begin_example
 ------ ----------------------------------------------------------------------------------
  Line   /var/folders/z5/sk1q5qj96xg4g87vkcp4hq9h0000gn/T/babel-TGYZJB/phpstan-ulqeYI.php
 ------ ----------------------------------------------------------------------------------
  4      Parameter $date of method HelloWorld::sayHello() has invalid type
         DateTimeImutable.
 ------ ----------------------------------------------------------------------------------


 [ERROR] Found 1 error

#+end_example

実装方法について

ob-phpstan は100行にも満たない小さなコードで実装されています。 https://github.com/emacs-php/ob-phpstan/blob/main/ob-phpstan.el

  • org-babel-temp-file で一時ファイルを作成
  • コードブロックの頭に <?php を追加
  • with-temp-file を使って一時ファイルに文字列を追加
  • org-babel-eval でphpstanを実行
(defun org-babel-execute:phpstan (body params)
  "Org mode fish evaluate function"
  (let ((tmp-file (org-babel-temp-file "phpstan-" ".php"))
        (body (concat "<?php\n" body))
        (level (or (cdr (assoc :level params)) org-babel-phpstan-level)))
    (with-temp-file tmp-file (insert (org-babel-expand-body:generic body params)))
    (org-babel-eval (format "%s analyze %s --level %s --no-progress"
                            org-babel-phpstan-command
                            (org-babel-process-file-name tmp-file)
                            level)
                    "")))

phpstanの場合、標準入力でプログラムを渡す方法ではなく、ファイルパスを指定する方法しかありません。 以前ob-fishを作ったが、 org-babel 周りの知識はからっきしなので ob-typescript を参考に実装しました。

Conclusion

melpaに申請をしたり、ドキュメントを整えたり、まだやることが残っているので順次進めていきます。

今回の実装を通じて org-babel 周りに詳しくなれました。 org-babel の実装方法やまとまったドキュメントは存在していないので機会があれば書こうと思いました。