typeprof で自分の書いたコードの間違いが分かるようにしたい

# はじめに

typeprof でソースコードの解析を行うと、自分の間違いとは関係のないエラーも出力されることがあります。

自分の知りたい間違いだけが出力されるようにするために、安直なツールを作成しました。

[tpoutcut](https://github.com/ktakase00/tpoutcut)

興味のないエラーを取り除いてもまだエラーが残っていれば、終了コードでそれがわかる、というものです。

# たとえば

## 間違いのあるコード

以下のコードには間違いが 2 つあります。

```ruby:app/tpoutcut.rb
class Tpoutcut
  def run
    app_params = AppParams.load
    result = make_result(app_params)

    puts result.contents

    make_exit_code(result_data)
  end

  private

  def make_result(app_params, _mode)
    result = Result.new(config: app_params.config)

    InputFile.open(app_params.input_path) do |file|
      file.each do |line|
        line.gsub!(/[\r\n]*$/, '')
        result.update(line)
      end
    end

    result
  end

  def make_exit_code(result)
    result.contents.empty? ? 0 : 1
  end
end
```

間違いは以下の 2 点です。

- make_result メソッド定義ではの引数が 2 つあるのに、呼び出し側が 1 つしか指定していない。
- make_exit_code メソッドの呼び出しで未定義の変数 result_data を参照している。

## Rubocop で試す

社内では Rubocop を活用しています。これらの間違いを Rubocop で検出できるでしょうか。

```sh
$ bundle exec rubocop --require rubocop-rspec app/tpoutcut.rb
Inspecting 1 file
.

1 file inspected, no offenses detected
```

検出されませんでした。Rubocop は Linter の役割を果たすものであり、ロジックの間違いは検出されません。

## typeprof で試す

Ruby 3.0 以降では、静的解析のツールとして、typeprof があります。試してみます。

```sh
$ typeprof --show-errors app/tpoutcut.rb
# TypeProf 0.21.3

# Errors
  : (中略)
app/tpoutcut.rb:11: [error] wrong number of arguments (given 1, expected 2)
app/tpoutcut.rb:15: [error] undefined method: Tpoutcut#result_data
  : (中略)

# Classes
  : (以下省略)
```

出力内容の `# Errors` セクションに、見つけたい間違いが出力されています。テストコードを書いていなくても間違いが検出されました。

# typeprof を単独で実行した場合

## 困る：無視してもよさそうなエラー出力がある

typeprof を実行してみます。

```
$ typeprof --show-errors typeprof.rbs app/*.rb
# TypeProf 0.21.3

# Errors
app/config.rb:13: [error] undefined method: singleton(YAML)#safe_load
app/config.rb:14: [error] unknown keyword: 
app/result.rb:11: [warning] inconsistent assignment to RBS-declared variable
app/tpoutcut.rb:11: [error] wrong number of arguments (given 1, expected 2)
app/tpoutcut.rb:15: [error] undefined method: Tpoutcut#result_data

# Classes
  : (以下省略)
```

出力されたエラー内容のうちいくつかは、無視してもよさそうです。「YAML に safe_load メソッドはない」などは、知らんがな、と言いたいところです。

## ちょっと困る：終了コードは変わらない

typeprof は型推論をするツールであって、少なくとも現時点では、間違いを検出するためのものではなさそうです。実行結果に `# Errors` のセクションがあってもなくても、終了コードは `0` になりました。

```sh
$ typeprof --show-errors typeprof.rbs app/*.rb
  :

$ echo $?
0
```

## つまり

- 興味のないモジュールについてのエラーは出力内容から除外できる
- エラーが見つかったことが終了コードでわかる

これができれば実用的になりそうです。

# tpoutcut

## 実行してみる

typeprof の実行結果は標準出力に出力されているので、これを読み取って、必要な部分だけ抜き出します。

[tpoutcut](https://github.com/ktakase00/tpoutcut) のリポジトリが `~/projects` にあるとします。

```sh
$ PATH=$PATH:~/projects/tpoutcut/bin

$ typeprof --show-errors typeprof.rbs app/*.rb | tpoutcut
```

たとえば以下のような内容が出力されます。`# Errors` セクションの内容だけが取り出されました。

```
app/config.rb:13: [error] undefined method: singleton(YAML)#safe_load
app/config.rb:14: [error] unknown keyword: 
app/result.rb:11: [warning] inconsistent assignment to RBS-declared variable
app/tpoutcut.rb:11: [error] wrong number of arguments (given 1, expected 2)
app/tpoutcut.rb:15: [error] undefined method: Tpoutcut#result_data
```

自前でやらなくても、typeprof コマンドのオプション引数で `# Errors` の内容だけを出力できるのかもしれませんが、わかりませんでした。

## 除外したいエラーを定義する

設定ファイルを用意します。出力内容から除外したいエラー内容の正規表現パターンを指定します。

```yaml:sample/sample01.yaml
section: Errors
excludes:
  - '\[warning\]'
  - 'undefined method: singleton\(YAML\)#safe_load'
  - 'app/config.rb:[\d]+: \[error\] unknown keyword: '
```

設定ファイルを指定して実行します。

```sh
$ typeprof --show-errors typeprof.rbs app/*.rb | tpoutcut --config "$(pwd)/tpoutcut.yaml"
```

以下のように実行結果が変わります。

```
app/tpoutcut.rb:11: [error] wrong number of arguments (given 1, expected 2)
app/tpoutcut.rb:15: [error] undefined method: Tpoutcut#result_data
```

検出したいエラーだけが出力されました。

## 終了コードを確認する

間違いが残っている状態で tpoutcut を実行すると、すなわち、`# Errors ` セクションに、除外対象ではないエラー内容が残っていると、終了コードは `1` になります。

```sh
$ typeprof --show-errors typeprof.rbs app/*.rb | tpoutcut --config "$(pwd)/tpoutcut.yaml"
app/tpoutcut.rb:11: [error] wrong number of arguments (given 1, expected 2)
app/tpoutcut.rb:15: [error] undefined method: Tpoutcut#result_data

$ echo $?
1
```

間違いを修正してから実行すると、終了コードが `0` になります。

```sh
$ typeprof --show-errors typeprof.rbs app/*.rb | tpoutcut --config "$(pwd)/tpoutcut.yaml"

$ echo $?
0
```

これで、自分の知りたい書き間違いを検出することができるかもしれません。

# おわりに

RBS を使用した静的型解析に Steep がありますが、すみません、まだ触っていません。今回は typeprof だけ見てみました。

Ruby でよく間違えるのは、メソッドの引数を増やしたのに呼び出し元は直していなかったとか、変数名が間違っているとか、そういうところになるかと思いますが、それをテストコードなしで検出できるのであれば、心強いです。

他のツールも見ながら、安全な開発ができるようにしていきたいです。

# 参考

- [typeprof](https://github.com/ruby/typeprof)
- [RBS](https://github.com/ruby/rbs)
- [型プロファイラ：抽象解釈に基づくRuby 3の静的解析](https://www.slideshare.net/mametter/ruby-3-232307836)
- [Steep](https://github.com/soutaro/steep)