【Otemachi.rb#12】Atomでrubocop-auto-correctを使う際の注意点


自己紹介

堀崎誠治


事の始まり

Otemachi.rb #10でボッチ演算子を知ったので使ってみたところ、rubocop-auto-correctが反応しなくなった。


rubocop-auto-correct

Atomの拡張機能。
Atom上でrubocopを-aオプションで実行してくれる。
(ファイル保存時に実行する設定もできる)


期待する動作


実際の動作


無反応。。


設定を変更して情報を増やす


⬇️



Ruby2.2で動作してる
(ボッチ演算子は2.3から)


RubocopのRubyバージョン特定方法

rubocopがrubyのバージョンを特定する優先順位は以下のとおり。

.rubocop.ymlのTargetRubyVersion の値 >
.ruby-versionの値 >
Gemfile.lockのRubyバージョンの値 >
デフォルト値(2.2)


当時の環境

  • .rubocop.ymlなし
$ ls .rubocop.yml ~/.rubocop.yml
ls: .rubocop.yml: No such file or directory
ls: /Users/user/.rubocop.yml: No such file or directory
  • .ruby-veversionあり
$ cat .ruby-version
2.3.1
  • Gemfile.lockにrubyのバージョン指定なし
$ grep -A 1 "RUBY VERSION" Gemfile.lock | wc -l
       0

手動で実行してみる

$ rubocop -a sample.rb
Inspecting 1 file
C

Offenses:

sample.rb:1:1: C: [Corrected] Style/FrozenStringLiteralComment: Missing magic comment # frozen_string_literal: true.
def sample(sample_arg)
^

1 file inspected, 1 offense detected, 1 offense corrected

手動なら動くので、拡張機能での実行時との差分を詰めていく


rubocop-auto-correctの実装

rubocop-auto-correct/lib/rubocop-auto-correct.coffee
  autoCorrectBuffer: (editor)  ->
    buffer = editor.getBuffer()

    tempFilePath = @makeTempFile("rubocop.rb")
    fs.writeFileSync(tempFilePath, buffer.getText())

    rubocopCommand = @rubocopCommand()
    command = rubocopCommand[0]
    args = rubocopCommand[1]
      .concat(['-a', tempFilePath])
      .concat(@rubocopConfigPath(buffer.getPath()))

    which command, (err) =>
      return @rubocopNotFoundError() if (err)
      rubocop = spawnSync(command, args, { encoding: 'utf-8', timeout: 5000 })


ログを埋め込んでみる

    args = rubocopCommand[1]
      .concat(['-a', tempFilePath])
      .concat(@rubocopConfigPath(buffer.getPath()))

//ログ埋め込み
    console.log (command + " " + args.join(" "))

実行されたコマンド

  • rubocop.ymlなし

/Users/user/.anyenv/envs/rbenv/shims/rubocop --format json -a /var/folders/09/xr1wrst90lb_295d2pprm6f80000gn/T/d-118916-6808-ed0mea.2qqbj/rubocop.rb

  • rubocop.ymlあり

/Users/user/.anyenv/envs/rbenv/versions/2.5.1/bin/rubocop --format json -a /var/folders/09/xr1wrst90lb_295d2pprm6f80000gn/T/d-1181111-42791-1nbrcij.sdpz/rubocop.rb --config /Users/user/develop/work/qiita/rubocop_auto/.rubocop.yml

  • HOMEディレクトリにのみ.rubocop.ymlあり

/Users/user/.anyenv/envs/rbenv/versions/2.5.1/bin/rubocop --format json -a /var/folders/09/xr1wrst90lb_295d2pprm6f80000gn/T/d-1181111-42791-qw10yv.5336/rubocop.rb --config /Users/user/.rubocop.yml


Rubocopの実装

rubocop/lib/rubocop/config.rb
    def target_ruby_version
      @target_ruby_version ||=
        if for_all_cops['TargetRubyVersion']
          @target_ruby_version_source = :rubocop_yml


          for_all_cops['TargetRubyVersion']
        elsif target_ruby_version_from_version_file
          @target_ruby_version_source = :ruby_version_file


          target_ruby_version_from_version_file
        elsif target_ruby_version_from_bundler_lock_file
          @target_ruby_version_source = :bundler_lock_file


          target_ruby_version_from_bundler_lock_file
        else
          DEFAULT_RUBY_VERSION
        end
    end

rubocop/lib/rubocop/config.rb
    def target_ruby_version_from_version_file
      file = ruby_version_file
      return unless file && File.file?(file)


      @target_ruby_version_from_version_file ||=
        File.read(file).match(/\A(ruby-)?(?<version>\d+\.\d+)/) do |md|
          md[:version].to_f
        end
    end
rubocop/lib/rubocop/config.rb
    def ruby_version_file
      @ruby_version_file ||=
        find_file_upwards(RUBY_VERSION_FILENAME, base_dir_for_path_parameters)
    end

rubocop/lib/rubocop/config.rb
    def ruby_version_file
      @ruby_version_file ||=
        find_file_upwards(RUBY_VERSION_FILENAME, base_dir_for_path_parameters)
    end

rubocop/lib/rubocop/config.rb
    # Paths specified in configuration files starting with .rubocop are
    # relative to the directory where that file is. Paths in other config files
    # are relative to the current directory. This is so that paths in
    # config/default.yml, for example, are not relative to RuboCop's config
    # directory since that wouldn't work.
    def base_dir_for_path_parameters
      @base_dir_for_path_parameters ||=
        if File.basename(loaded_path).start_with?('.rubocop') &&
           loaded_path != File.join(Dir.home, ConfigLoader::DOTFILE)
          File.expand_path(File.dirname(loaded_path))
        else
          Dir.pwd
        end
    end


ログを仕込んでみる

rubocop/lib/rubocop/config.rb
        File.open("/Users/user/rubocop_auto.out", "a") {|f|
          f.puts "Dir.pwd: #{Dir.pwd}"
        }


結果

/Users/user/rubocop_auto.out
Dir.pwd: /

rootディレクトリから実行されていたので設定ファイルが見つけられていなかった


あれ?


File.basename(loaded_path).start_with?('.rubocop')

.rubocopで始まるファイルさえあればいい


$ wc -l .rubocop.yml
       0 .rubocop.yml
$ cat .ruby-version
2.3.1


動いた!


調査結果

ファイル 結果 ファイル 結果
1 .rubocop.yml 6 1+3
2 ~/.rubocop.yml × 7 1+4
3 .ruby-version × 8 1+5
4 ~/.ruby-version × 9 2+3 ×
5 Gemfile.lock × 10 2+4 ×
11 2+5 ×

※6~11の.rubocop.ymlは空ファイル


まとめ

  • --configオプションを指定しない場合はカレントディレクトリからファイルを探す。
  • atomから見たカレントディレクトリはrootディレクトリ。
  • プロジェクトディレクトリに.rubocop.ymlを配置する以外の手段は不可。
  • 拡張機能側で~/.rubocop.ymlを探してコマンドに渡しているがコマンド側で受け付けていない。(コマンド側の動きが変わった?)
  • rbenvを使っているならtouch .rubocop.ymlするだけで使える。
  • とはいえデフォルト設定ではrubocopの指摘は厳しいので.rubocop.ymlは設定したほうがいい。

参考

  • RuboCopが静的解析のRubyバージョンを決める流れ