はじめに
typeprof でソースコードの解析を行うと、自分の間違いとは関係のないエラーも出力されることがあります。
自分の知りたい間違いだけが出力されるようにするために、安直なツールを作成しました。
興味のないエラーを取り除いてもまだエラーが残っていれば、終了コードでそれがわかる、というものです。
たとえば
間違いのあるコード
以下のコードには間違いが 2 つあります。
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 で検出できるでしょうか。
$ 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 があります。試してみます。
$ 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
になりました。
$ typeprof --show-errors typeprof.rbs app/*.rb
:
$ echo $?
0
つまり
- 興味のないモジュールについてのエラーは出力内容から除外できる
- エラーが見つかったことが終了コードでわかる
これができれば実用的になりそうです。
tpoutcut
実行してみる
typeprof の実行結果は標準出力に出力されているので、これを読み取って、必要な部分だけ抜き出します。
tpoutcut のリポジトリが ~/projects
にあるとします。
$ 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
の内容だけを出力できるのかもしれませんが、わかりませんでした。
除外したいエラーを定義する
設定ファイルを用意します。出力内容から除外したいエラー内容の正規表現パターンを指定します。
section: Errors
excludes:
- '\[warning\]'
- 'undefined method: singleton\(YAML\)#safe_load'
- 'app/config.rb:[\d]+: \[error\] unknown keyword: '
設定ファイルを指定して実行します。
$ 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
になります。
$ 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
になります。
$ typeprof --show-errors typeprof.rbs app/*.rb | tpoutcut --config "$(pwd)/tpoutcut.yaml"
$ echo $?
0
これで、自分の知りたい書き間違いを検出することができるかもしれません。
おわりに
RBS を使用した静的型解析に Steep がありますが、すみません、まだ触っていません。今回は typeprof だけ見てみました。
Ruby でよく間違えるのは、メソッドの引数を増やしたのに呼び出し元は直していなかったとか、変数名が間違っているとか、そういうところになるかと思いますが、それをテストコードなしで検出できるのであれば、心強いです。
他のツールも見ながら、安全な開発ができるようにしていきたいです。