Help us understand the problem. What is going on with this article?

precommitで行うeslintのストレスをなくす

More than 1 year has passed since last update.

なにができるか

  • commit前にcommitされるファイルで差分のあるものだけをチェックする
  • eslintの進捗が確認できる

前提知識

静的コード解析を取り入れている現場は多い。
そして、JSだとeslint --fix + lint-stagedで、変更分に対して、静的コード解析+コード修正を行うことが割とスタンダードだ。
そして、huskyを使って、コミット前にコードチェックを強制する光景もよく見る。

コミット前に Lint を強制するなら lint-staged が便利」はもろにその話。

不満

lint-stagedは、対象のプロジェクト全体に対し、インデックスにのっていない差分まですべてチェックしているようなので、機能を作ってからcommitを分けるスタイルの自分だとcommitのたびに時間がかかる。1

そして、そのときの進捗もわからないので、ちょっと別のことをやったりしていて集中が途切れたりした。

こうしたかった

  • commitをされる分だけ、チェックしたい
    • ファイル指定でcommitする場合はファイル単位でチェック
  • commitがどれくらいで終わるかを確認したい

こんなスクリプトを書いた

https://gist.github.com/tomoyamachi/8511251b4dc47abaf07cec6799136c02

precommit.js
const child_process = require('child_process');
const path = require('path');

let exitCode = 0;

const errorLog = (stdout) => {
  const error = stdout.toString('utf8');
  if (error.length > 0) {
    console.log('\x1b[31m', error);
    exitCode = 1;
  }
};

const getChangedFiles = () => {
  const changedFiles = [];
  try {
    const str = child_process
      .execSync('git diff --cached --name-status | grep "\\.jsx\\?$"')
      .toString('utf8');
    // delimiter newline
    const changedLines = str
      .split(/(\r?\n)/g)
      .filter(line => !(line === '\n' || line === '\r' || line.length < 1));

    changedLines.forEach((l) => {
      // data = [gitStatus, fileName];
      const data = l.split('\t');
      // exclude `Delete` status files. To avoid `checkFileChanged` error
      if (data[0] !== 'D') {
        changedFiles.push(data[1]);
      }
    });
  } catch (e) {
    errorLog(e.stdout);
  }
  return changedFiles;
};

const lintFiles = (files) => {
  let progress = 0;
  files.forEach((file) => {
    progress += 1;
    console.log(`${progress}/${files.length} : ${file}`);
    try {
      child_process
        .execSync(
          `${path.join(__dirname, 'node_modules/.bin/eslint')} \
              -c ${path.join(__dirname, '.eslintrc')} \
              --ignore-path ${path.join(__dirname, '.eslintignore')} \
              --color --fix ${file}`
        )
        .toString('utf8');
    } catch (e) {
      errorLog(e.stdout);
    }
  });
};

// Stop commit if file changed by `eslint:fix`
const checkFileChanged = (files) => {
  try {
    const changedFiles = files.join(' ');
    const str = child_process
      .execSync(`git diff --name-only ${changedFiles}`)
      .toString('utf8');
    if (str.length > 0) {
      errorLog(`eslint fix changed following files : ${str}`);
      errorLog('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
      errorLog('!!!!!! Please commit again !!!!!!');
      errorLog('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
    }
  } catch (e) {
    errorLog(e.stdout);
  }
};

const changedFiles = getChangedFiles();
if (changedFiles.length > 0) {
  lintFiles(changedFiles);
  checkFileChanged(changedFiles);
}

process.exit(exitCode);

huskyを利用しているのであれば、package.jsonに以下のコードを入れるだけ。

"scripts": {
  "precommit" : "node ./precommit.js"
}

実行結果

eslint --fix の実行でファイルが変更された場合、commitが失敗するようになっている。失敗したら、整形後のファイルに対して再度commitが必要。

ローカルにてprettierを利用して、コード整形をしており、fixによる修正があまり発生しない前提で利用している。チェックするファイルが全4ファイルで、残り何ファイルチェックする必要があるかわかる。

% git commit -m 'commit message' .
Found '/Users/amachi/programs/project/.nvmrc' with version <v6.11.5>
Now using node v6.11.5 (npm v3.10.10)
husky > npm run -s precommit (node v6.11.5)

1/4 : app/js/containers/BaseRouter.jsx
...(中略)...
3/4 : app/js/lib/__tests__/getSubDomain.test.js
4/4 : app/js/lib/getSubDomain.js
[index_design fa9d7c2] commit message
 4 files changed, 97 insertions(+)
 create mode 100644 app/js/lib/__tests__/getModuleName.test.js
 create mode 100644 app/js/lib/__tests__/getSubDomain.test.js
 create mode 100644 app/js/lib/getSubDomain.js

本当は、precommitの間に変更されたファイルをそのままHEADに反映したいが、綺麗に実装する方法がわからないので、詳しい人に教えて欲しい。

取り入れた結果

体感速度があがったので、コミット中に他の用事をすることが減った。集中も途切れないで済むので、効率は上がったと信じている。

lint-stagedで同じようなことをしたかったが、現在そのようなオプションが見当たらなかったので、本当にないのであれば作ろうかと思っている。



  1. 挙動からみただけなので正しいかは不明だが、addするまえのステージにないファイルもチェックされる。lint-stagedという名前がついているのに、ステージに乗ってないものもチェックされているのがよく分かっていない... 

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away