LoginSignup
7
1

More than 1 year has passed since last update.

【Ruby3.1】error_highlight gem について

Posted at

はじめに

Ruby3.1で導入された error_highlight について軽く調査したので記事にまとめました。

  • Ruby3.1で導入されたバックトレース中の詳細なエラー位置を表示してくれる組み込みの gem
  • デフォルトで有効
    • --disable-error_highlight コマンドラインオプションを指定することで無効化できる
  • その他注意点
    • 全角文字が含まれると表示されない
    • 1行の文字が長すぎるとうまくハイライトできない
    • irb上では動作しない

Ruby3.0 までのエラー出力

before
$ ruby -e '1.time {}'
-e:1:in `<main>': undefined method `time' for 1:Integer (NoMethodError)
Did you mean?  times

Ruby3.1 以降のエラー出力

after
$ ruby -e '1.time'
-e:1:in `<main>': undefined method `time' for 1:Integer (NoMethodError)

1.time
 ^^^^^
Did you mean?  times

^ でエラーの位置が強調されています!!

error_highlight の API ErrorHighlight.spot を利用する

ErrorHighlight クラスの spot が利用できます。
(この API は実験的なものであり、将来変更される可能性があります。)

sample.rb
class Dummy
  def test(_dummy_arg)
    node = RubyVM::AbstractSyntaxTree.of(caller_locations.first, keep_script_lines: true)
    ErrorHighlight.spot(node)
  end
end

pp Dummy.new.test(42)

以下のように、 spot に渡した node の場所の情報が取得できます。

$ ruby sample.rb
{:first_lineno=>8,
 :first_column=>12,
 :last_lineno=>8,
 :last_column=>17,
 :snippet=>"pp Dummy.new.test(42)\n",
 :script_lines=>
  ["class Dummy\n",
   "  def test(_dummy_arg)\n",
   "    node = RubyVM::AbstractSyntaxTree.of(caller_locations.first, keep_script_lines: true)\n",
   "    ErrorHighlight.spot(node)\n",
   "  end\n",
   "end\n",
   "\n",
   "pp Dummy.new.test(42)\n"]}

上記の例では、8行目の12から17列目まで。
この情報を元に、以下のように表示しています。

pp Dummy.new.test(42) # <- Line 8
#           ^^^^^       <- Column 12--17

エラーのフォーマットを変更する

ErrorHighlight.formatter を上書きすることで、エラーのフォーマットが変更できます。
spot の結果を受けとるメソッド message_for を定義したオブジェクトを作成します。

formatter_sample.rb
formatter = Object.new
def formatter.message_for(spot)
  marker = " " * spot[:first_column] + "^" + "~" * (spot[:last_column] - spot[:first_column] - 1)

  "\n\n#{ spot[:snippet] }#{ marker }"
end

ErrorHighlight.formatter = formatter

1.time {}
$ ruby formatter_example.rb 
formatter_example.rb:10:in `<main>': undefined method `time' for 1:Integer (NoMethodError)

1.time {}
 ^~~~~
Did you mean?  times

出力が変化しました!

実装

error_highlight gem は以下のようなファイル構成になっています。

  • error_highlight.rb
  • error_highlight/
    • base.rb
    • core_ext.rb
    • formatter.rb
    • version.rb

base.rb に以下のような分岐があり、node の種類ごとに処理を行っています。

      case @node.type

      when :CALL, :QCALL
        case @point_type
        when :name
          spot_call_for_name
        when :args
          spot_call_for_args
        end

core_ext.rbprepend により TypeErrorArgumentError のメソッド上書きすることで、この機能を提供しているらしいです。

    TypeError.prepend(CoreExt)
    ArgumentError.prepend(CoreExt)

参考文献

おまけ

Qiita株式会社では Ruby3 新機能の社内勉強会を開催しており、この記事もその一環で記載しました!
Devトークも募集しておりますので、社内勉強会についてや Ruby3 新機能についてお話ししましょう!

7
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
1