RubocopというRubyの文法チェッカがありますが、
これをCircleCI等のCIツールで実行してチェックしている人も多いと思います。

Rubocop:
https://github.com/bbatsov/rubocop

Rubocopに限らずですが、
このような静的チェックツールは、既存のリポジトリに追加しようとすると、
既存コードに対するエラーが存在しているため。
開発を一度止めて全体のリファクタリングとテストを行う、
除外指定をして既存コードを無視して適用するなどの対応が必要になり。
なかなか、導入に踏み切れないこともあると思います。

このエントリでは、
既存プロジェクトに対してスムーズに導入する方法として。
PullRequestの新規or修正箇所でエラーがあった場合のみ、
(既存コードは無視して)CIでエラーとなるようにする方法を提案します。

# ちなみにRubocopに限らず、どんなツールでも同じ方法が使えるはずです。

railsのプロジェクト作成から、順に流れを追って説明していきます。

プロジェクト作成とrubocop導入

rails newしたプロジェクトのGemfileに、
以下のようにrubocopを追加、bundle installします。

Gemfile抜粋

group :development do
  gem 'rubocop'
end

gemのインストール

$ bundle install --path vendor/bundle

次にrubocopを実行します。

$ bundle exec rubocop
Inspecting 37 files
.CCCC.CC..CCC.C.CC..CCCC.CC..C..C...C

Offenses:
※途中省略※

db/seeds.rb:6:81: C: Metrics/LineLength: Line is too long. [81/80]
#   movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }])
^

37 files inspected, 52 offenses detected

たくさんエラーが検出されますが、一旦無視してgit commit & pushします。

$ git commit -a -m "add rubocop"
$ git push origin master

これでrubocopでエラーになる既存コードが存在するリポジトリが出来ました。

新規or修正箇所のエラー抽出スクリプト作成

ブランチを切って、修正を加えます(scaffoldで適当に)。

$ git checkout -b add_scaffold
$ bundle exec rails g scaffold note titile:string body:string

rubocopを実行してみると、(既存と併せて)76エラー検出しました。

$ bundle exec rubocop
Inspecting 47 files
.CCC.C.CC.C.CC...CCCCC.C.CC..CCCC.CC..C..C...CC

Offenses:
※途中省略※

db/migrate/20180129154129_create_notes.rb:1:1: C: Style/Documentation: Missing top-level class documentation comment.
class CreateNotes < ActiveRecord::Migration[5.1]
^^^^^

47 files inspected, 76 offenses detected

git add & commitします。

$ git add .
$ git commit -a -m "add scaffold"

次に新規or修正箇所のエラーのみ抽出するシェルスクリプトを作成します。

bin/diff_filter.sh

#!/bin/bash

BASE_REMOTE=origin
BASE_BRANCH=master

git fetch $BASE_REMOTE $BASE_BRANCH
git branch $BASE_BRANCH $BASE_REMOTE/$BASE_BRANCH

diff_list=()
commit_list=`git --no-pager log --no-merges $BASE_REMOTE/$BASE_BRANCH...HEAD | grep -e '^commit' | sed -e "s/^commit \(.\{8\}\).*/\1/"`
for f in `git --no-pager diff $BASE_REMOTE/$BASE_BRANCH...HEAD --name-only`; do
  for c in $commit_list; do
    diffs=`git --no-pager blame --show-name -s $f | grep $c | sed -e "s/^[^ ]* *\([^ ]*\) *\([0-9]*\)*).*$/\1:\2/"`
    for ln in $diffs; do
      diff_list+=( $ln )
    done
  done
done

err_count=0
while read -r ln; do
  for m in ${diff_list[@]}; do
    if [[ ${ln} =~ ^$m ]]; then
      echo $ln
      err_count=$((err_count+1))
      break
    fi
  done
done < /dev/stdin

if [ $err_count -ne 0 ]; then
  echo -e "\033[0;31mERROR FOUND, check above messages.\033[0;39m"
  exit 1
fi

rubucopの出力をこのシェルスクリプトでフィルターして、
新規or修正部分のエラーに絞り込みます。

$ bundle exec rubocop | bash bin/diff_filter.sh
※途中省略※
test/controllers/notes_controller_test.rb:41:8: C: Style/StringLiterals: Prefer single-quoted strings when you don't need string interpolation or special symbols.
db/migrate/20180129154129_create_notes.rb:1:1: C: Style/Documentation: Missing top-level class documentation comment.
ERROR FOUND, check above messages.

新規or修正箇所に対するCircleCIでのrubocop実行

後は、このシェルスクリプトをCircleCIで呼び出すだけです。

CircleCIのconfig.ymlを以下のように追加します。

.circleci/config.yml

version: 2
jobs:
  build:
    docker:
       - image: circleci/ruby:2.4.1-node-browsers
    working_directory: ~/repo
    steps:
      - checkout
      - restore_cache:
          keys:
          - v1-dependencies-{{ checksum "Gemfile.lock" }}
          - v1-dependencies-
      - run:
          name: install dependencies
          command: |
            bundle install --jobs=4 --retry=3 --path vendor/bundle
      - save_cache:
          paths:
            - ./vendor/bundle
          key: v1-dependencies-{{ checksum "Gemfile.lock" }}

      # Database setup
      - run: bundle exec rake db:create
      - run: bundle exec rake db:schema:load

      # run rubocop
      - run:
          name: run rubocop
          command: |
            bundle exec rubocop | bash bin/diff_filter.sh

このファイルをGitHubにpushします。

$ git add .
$ git commit -a -m "add circleci"
$ git push origin add_scaffold

CircleCIを設定すると、以下の画面のようにCIが失敗します。

また、ここでエラーとなっている部分を修正すれば、
既存コードに問題があっても、CIは成功するようになります。

以上のような方法をとると、
既存プロジェクトに対しても、静的チェックツールが導入しやすくなります。

また、除外指定してしまうと、修正するタイミングを逃してしまうことも多いですが。
修正箇所に対してチェックすると、
改修のタイミングでリファクタリング出来るというメリットもあるかと思います。