Dockerで言語サーバーのPhanを動かしてみた

vimの勉強中にLSP(Language Server Protocol)を知って、
Dockerでいろんな言語サーバーが簡単に起動できたら、
便利じゃない？っと思ったので、
今回はDockerで言語サーバーのPhanを動かしてみた。

- LSP
https://microsoft.github.io/language-server-protocol/

- Phan
https://github.com/phan/phan

# できたやつ

https://github.com/tubutubumustard/phan_docker

あとはできたやつの説明

# ディレクトリ構成

```
phan/
  .phan/
    config.php
  src/
    Foo.php
Dockerfile
docker-compose.yml
```

# docker-compose.yml

```yaml:docker-compose.yml
version: '3.7'
services:
  phan:
    build: ./phan
    volumes:
      - type: bind
        source: ./phan/.phan
        target: /analyze/.phan
        volume:
          nocopy: true
      - type: bind
        source: ./phan/src #ここに解析したいディレクトリを設定する
        target: /analyze/src
        volume:
          nocopy: true
```

できたやつでは、`phan/src`ディレクトリが解析対象となるように、設定している。
ディレクトリを変更すれば解析対象を変更できる

# Dockerfile

```dockerfile:Dockerfile
FROM php:7.3.10-cli-alpine3.10

WORKDIR /analyze

ENV PHPIZE_DEPS \
        autoconf \
        dpkg-dev dpkg \
        file \
        g++ \
        gcc \
        libc-dev \
        make \
        pkgconf \
        re2c

RUN apk update && apk add --no-cache --virtual .build-deps $PHPIZE_DEPS; \
        pecl install ast-1.0.1; \
        echo 'extension=ast.so' > /usr/local/etc/php/php.ini; \
        apk del --no-network .build-deps

ENV COMPOSER_ALLOW_SUPERUSER=1

RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"; \
        php -r "if (hash_file('sha384', 'composer-setup.php') === 'a5c698ffe4b8e849a443b120cd5ba38043260d5c4023dbf93e1558871f1f07f58274fc6f4c93bcfd858c6bd0775cd8d1') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"; \
        php composer-setup.php; \
        php -r "unlink('composer-setup.php');"; \
        php composer.phar require "phan/phan:2.x"

CMD [ "./vendor/bin/phan"]
```

PhanのGetting Startedを参考にして必要なものを揃える
https://github.com/phan/phan/wiki/Getting-Started

# PHPの7.1以上が必要なので、PHPの7.3.10のイメージを使用する

```dockerfile
FROM php:7.3.10-cli-alpine3.10
```

# php-ast 1.0.1以上が必要なので、php-astをpeclでインストールする

```dockerfile
ENV PHPIZE_DEPS \
        autoconf \
        dpkg-dev dpkg \
        file \
        g++ \
        gcc \
        libc-dev \
        make \
        pkgconf \
        re2c

RUN apk update && apk add --no-cache --virtual .build-deps $PHPIZE_DEPS; \
        pecl install ast-1.0.1; \
        echo 'extension=ast.so' > /usr/local/etc/php/php.ini; \
        apk del --no-network .build-deps
```

今回のPHPのイメージはalpineをベースイメージとしているので、
peclを使用するために必要なものをPHPIZE_DEPSという環境変数にいれて、

```dockerfile
ENV PHPIZE_DEPS \
        autoconf \
        dpkg-dev dpkg \
        file \
        g++ \
        gcc \
        libc-dev \
        make \
        pkgconf \
        re2c
```

--virtualオプションで、 .build-deps という名前をつけてインストールしている

```shell
apk add --no-cache --virtual .build-deps $PHPIZE_DEPS
```

名前をつけると下記のように名前を指定するだけで、パッケージを削除できる
（削除する方法を知ってしまったので、遊びでイメージを小さくしています）

```shell
apk del --no-network .build-deps
```

削除の方法は、今回使用しているPHPのイメージのdockerfileを参考にしたよ
https://github.com/docker-library/php/blob/29a47a90c8569bcb5e1960fcebab323bf2d89816/7.3/alpine3.10/cli/Dockerfile

php-astのREADME.mdより、
https://github.com/nikic/php-ast
インストールして、php.iniにextension=ast.soを設定している


```shell
pecl install ast-1.0.1; \
        echo 'extension=ast.so' > /usr/local/etc/php/php.ini; \
```

# Phan 2.x をComposerでインストールする

```dockerfile
RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"; \
        php -r "if (hash_file('sha384', 'composer-setup.php') === 'a5c698ffe4b8e849a443b120cd5ba38043260d5c4023dbf93e1558871f1f07f58274fc6f4c93bcfd858c6bd0775cd8d1') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"; \
        php composer-setup.php; \
        php -r "unlink('composer-setup.php');"; \
        php composer.phar require "phan/phan:2.x"
```

まず、composer.pharをインストールする

```shell
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"; \
        php -r "if (hash_file('sha384', 'composer-setup.php') === 'a5c698ffe4b8e849a443b120cd5ba38043260d5c4023dbf93e1558871f1f07f58274fc6f4c93bcfd858c6bd0775cd8d1') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"; \
        php composer-setup.php; \
        php -r "unlink('composer-setup.php');"; \
```

composer.pharでPhan 2.x をインストールする

```shell
php composer.phar require "phan/phan:2.x"
```

# .phan/config.php

`phan/.phan/config.php`はPhanの解析の設定ファイル
https://github.com/phan/phan/wiki/Incrementally-Strengthening-Analysis

```shell
vendor/bin/phan --init --init-level=1
```

解析が一番厳しい設定のファイルを作成しました

設定の項目の内容について
https://github.com/phan/phan/wiki/Phan-Config-Settings

# 解析してみる

`$three = 3;`って変数が使われてなくて、
`var_dum($one);`がタイポしている、
できたやつの`phan/src/Foo.php`を解析する

```php:Foo.php
<?php

class Foo
{
    public function bar()
    {
        $one   = 1;
        $two   = 2;
        $three = 3;
        var_dum($one);
        var_dump($one + $two);
    }
}
```

```
$ docker-compose run phan
src/Foo.php:9 PhanUnusedVariable Unused definition of variable $three
src/Foo.php:10 PhanUndeclaredFunction Call to undeclared function \var_dum() (Did you mean \var_dump())
```

変数が使われていないのと、var_dumpじゃないって教えてくれてる

# おわり

LSPとして使うにはまだ、時間がかかりそう。
クライアントの設定がむずい。