ES Modulesで、他の箇所から参照されていないのにexportされている関数を探すワンライナー

eslintの`no-unused-vars`というルールを使うと、使われていない関数や変数を簡単に検出できます。よっぽどの理由がない限りは、コードの見通しを良い状態に保つためにも、[意図しない不具合の発生を防ぐためにも](http://www.clear-code.com/blog/2018/6/7.html)、自分は`no-unused-vars`の警告が出た部分は片っ端から消すようにしています。

しかしながら、それでも見落としが発生する場合があります。それは、ES Modulesでの`export`された関数です。

ES Modulesのモジュール定義の様式にはいくつかのやり方がありますが、自分が好んで使っているのは以下のような、モジュール関数をたくさん定義してそれぞれ`export`するスタイルです。

```javascript:モジュール定義
export function getXXX() {
  ...
}

export function setXXX(aValue) {
  ...
}
```

```javascript:参照する側
// exportされた関数全てをMyModuleという名前のオブジェクトのメソッドとして呼べる形でimport
import * as MyModule from './my-module.js';

// モジュール関数の呼び出し
MyModule.setXXX(true);
```

このスタイルだと、「他の箇所から参照されていないのにexportされているモジュール関数」という物ができてしまいがちです。何故でしょうか。

ES Modulesでは、`export`されていないオブジェクトには他のモジュールから触れません。言い換えると、`export`されているオブジェクトは他から使われる可能性があります。そのため、「実はどこからも参照されてないんだけど、他から触られる可能性があるから`no-unused-vars`の警告は出ない」関数というゴミが残ってしまうという訳です。他のプロダクトに組み込んで使うライブラリでないなら、こういう物もガンガン消したいですよね。

ということで、Gitで管理されているリポジトリを対象に、こういったゴミ関数を検出して`export`しなくするワンライナーを書いてみました。UbuntuのBashで実行する想定です（自分はWSLで実行しました）。macOSとかBSDとかだとオプションを変えないといけないかも。

```bash
git grep -E '^export.+function ([^(]+)' | while read line; do file="$(echo "$line" | cut -d : -f 1)"; name="$(echo "$line" | cut -d : -f 2 | sed -r -e 's/^export.+function ([^(]+)\(.+$/\1/')"; git grep -E "\b$name\b" | grep -v "$file" > /dev/null || (echo "$file / $name"; sed -r -i -e "s/^export (.+ $name)\(/\1(/" "$file"); done
```

長いので適宜改行し、つつそれぞれの箇所の意味を解説してみます。

```bash
# exportされている関数を探す
git grep -E '^export.+function ([^(]+)' |
  # その結果を1行ずつ取り出す
  while read line;
  do
    # git grepの結果から、ファイルパスの部分を取り出す
    file="$(echo "$line" |
              # 「:」で区切った左側を取り出す
              cut -d : -f 1)";
    # git grepの結果から、関数名の部分を取り出す
    name="$(echo "$line" |
              # 「:」で区切った右側を取り出す
              cut -d : -f 2 |
              # 不要部分を除去して関数名だけにする
              sed -r -e 's/^export.+function ([^(]+)\(.+$/\1/')";
    # 他のメソッドや関数の名前の一部ではない、単独の名前としてその名前が出てくる箇所を探す（「\b」は単語の区切りを意味する）
    git grep -E "\b$name\b" |
      # その関数が定義されているファイルは除外
      grep -v "$file" \
        # grepの出力は使わないので捨てる
        > /dev/null || # grepの結果が0件だったら次を実行
      # 「(～)」で囲って、これ全体を「grepの結果が0件だったら次を実行」の対象にする
      (echo "$file / $name";
       # 関数を定義している箇所から「export」を取り除く
       sed -r -i -e "s/^export (.+ $name)\(/\1(/" "$file");
  done
```

[実際にこのワンライナーでゴソッと関数をprivateにしたコミットの例](https://github.com/piroor/treestyletab/commit/e7a4a9306f223086d19f0c41219044815a5886f7)も併せて見てみて下さい。

後は、[eslintで`no-unused-vars`の警告が出た関数を適宜削除](https://github.com/piroor/treestyletab/commit/02820b42e571de1978f525814f69679fa7dbdf0c)すれば、どこからも使われていない関数をプロダクト内から一掃できます。

----

IDEを使っていればこういう事もIDE側の操作で一発でできちゃうんだろうなあ……と思いながら、IDEの使い方を覚えるのが億劫で、ワンライナーでやってしまっています。

逆に言うと、IDEでこういう機能がサポートされていない場合、効率よく＆見落としなくやろうと思うと、何らかのプログラムを作ったり、こういうワンライナーで処理したりといった方法を取るのがお薦めです。こんな事いちいち人力でやっていると、時間も気力も消耗してしまいます。

自分もかつてはシェルには苦手意識があって、時間をかけて人力でやったり、あるいは比較的得意な言語であったJavaScriptで無理矢理やってみたり、という具合でした。今では、機械的な作業をワンライナーに任せることで、時間と気力の余裕が生まれ、自分自身はより難しい問題の解決に集中して取り組めているのではないかと思っています。

「いまどきシェルなんて……」「サーバーなんて触らないし……」「Windowsだし関係ないし……」と思う人もいると思いますが、覚えておくと（日々の開発でも）できる事の幅が広がりますし、Windows 10なら今は[WSL](https://system-admin-girl.com/comic/begins/sp-wsl/)で手軽にBashを使える状況になってきています。少なくとも、今も人力作業で消耗しているという人は、シェルのコマンド操作を覚えて普段の作業を楽にしてみてはいかがでしょうか？ 拙著[シス管系女子](https://system-admin-girl.com/)では、自分で自信を持ってシェルのコマンド列を組み立てられるようになる事を目指して、各コマンドのはたらきを図解しながら説明しています。「シス管」と題してはいますが、コマンド操作をする機会があるすべての人におすすめできる内容なので、挫折した経験のある方、苦手意識が抜けきらないという方は、一度見てみて頂ければ幸いです。

----

ところで、シェルでワンライナーというと`xargs`を使うやり方を真っ先に紹介するケースが多いと思うのですが、[シス管系女子](https://system-admin-girl.com/)では`xargs`ではなく[`while`と`read`の組み合わせ](77233173707a0baa6360)の方を紹介しています。これは以下のような理由からです。

 * `xargs`だと、パスに空白文字が含まれる場合に特別な対応が必要である。`while`ループでは、特別な対応を覚えなくてもよい。
 * `xargs`だと、この例のように複数の事をループの中でやるのは大変（それらを一旦関数にする必要がある）。`while`ループでは、ループ処理の中に複数ステップが必要な処理を書く場面にも対応しやすい。

このように`xargs`には色々落とし穴があるので、「`xargs`でないと解決できない問題」に突き当たるまでは`xargs`のことは教えないのが初学者フレンドリーなのではないか、というのが自分の考えです。いかがでしょうか。