rubocop-jpのissuesにあげたものを、実装してみました。
このCopが対象とする問題
eval
族1のメソッドには、コードの位置情報を渡すことが出来ます。
この位置情報は主に例外が上がった際のバックトレースの表示に役立ちます。
https://docs.ruby-lang.org/ja/latest/method/Kernel/m/eval.html
例
fname = 'file name!' lnum = 100 eval "raise", binding, fname, lnum # file name!:100:in `<main>': unhandled exception # from test.rb:3:in `eval' # from test.rb:3:in `<main>'
このように、eval
メソッドにはファイル名と行番号を渡すことができます。
これには任意の値を渡すことが可能ですが、eval
に直接Stringリテラルを渡す場合、常識的に考えてeval
の呼び出し元のファイル名と、そのStringリテラルがある行の番号を渡すべきでしょう。
# test.rb eval "raise", binding, 'test.rb', 2 # test.rb:2:in `<main>': unhandled exception # from test.rb:2:in `eval' # from test.rb:2:in `<main>'
これで正しいファイル名がバックトレースに表示されるようになりました。ですが、このままではファイルをリネームしたり、行を足したりしただけでこのバックトレースが壊れてしまいます。
この問題を避けるために、__FILE__
と__LINE__
を使用することができます。
これらは疑似変数と呼ばれる特殊な変数です。__FILE__
は現在のソースファイル名、__LINE__
はそれが書かれた位置の行番号が入っています。
これらを使用すると、先程のコードは次のように書くことが可能です。
# test.rb eval "raise", binding, __FILE__, __LINE__ # test.rb:2:in `<main>': unhandled exception # from test.rb:2:in `eval' # from test.rb:2:in `<main>'
なお、ヒアドキュメントを使う場合や、引数の途中で改行を行う場合には注意が必要です。
# ヒアドキュメントを使う場合はコードが __LINE__ を書いた次の行から始まるため、__LINE__ + 1 する必要がある。 eval <<~RUBY, binding, __FILE__, __LINE__ + 1 def foo do_something end RUBY # evalの引数を改行する場合はコードが __LINE__ を書いた行より前に来るので、__LINE__ - 1 する必要がある。 eval "def foo; do_something; end", binding, __FILE__, __LINE__ - 1
このCopはeval
メソッドの引数の位置情報が正しく渡されているかを検査します。
このCopは常に守る必要があるの?
いいえ、常に守る必要はありません。 ですが、次に上げるいくつかの例外ケースをのぞいては、守っていたほうが良い効果を得られるでしょう。
eval
内のコードで例外が発生しない場合
例えば、以下のコードでは例外が発生しません2。
eval <<~RUBY def foo 1 end RUBY
例外が発生しないため、ファイル名を足す必要性はそこまで高くありません。
ただし、このようなケースでもファイル名を足しておくことにより、pry
などでメソッドの定義位置を探すことができるようになるメリットがあります。
# test.rb eval <<~RUBY def foo 1 end RUBY eval <<~RUBY, binding, __FILE__, __LINE__ def bar 1 end RUBY
[1] pry(main)> require_relative 'test.rb' => true [2] pry(main)> $ foo Error: Cannot open "(eval)" for reading. [3] pry(main)> $ bar From: /tmp/tmp.tOBiT2XfMg/test.rb @ line 7: Owner: Object Visibility: private Number of lines: 5 eval <<~RUBY, binding, __FILE__, __LINE__ def bar 1 end RUBY
コードを文字列リテラルとして直接渡すわけではない場合
動的にRubyのコードを生成してeval
メソッドに渡す場合は、__FILE__
などを渡す必要性は薄いでしょう。
def define_cool_method(something) code = generate_some_ruby_code(something) eval code end define_cool_method(42)
このようなケースではeval
の行にコードがないため、__FILE__
や__LINE__
を指定してもあまり意味がありません。
また、このケースに関してはこのCopは警告を出さないようになっています。
このCopはいつから使えるの?
RuboCopの次のリリースから使うことが出来ます。しばらくお待ちください。
https://docs.ruby-lang.org/ja/latest/doc/spec=2fvariables.html#pseudo