オレオレgoimportsを作ってみた


やりたいこと、作ったもの

goimports最高

goimportsはGoのCLIツールで、*.goファイルに対して↓を実行してくれます。

  • importしていない外部パッケージをimportしてくれる
  • 使っていないimportは削除してくれる
  • 最後にgofmtもかけてくれる

でもgoimportsは変な空行を入れてくる

github.com/golang/go/issues/20818 のIssueにある通り、goimportsは変な空行を入れてきます。
こうなっている理由は単に「仕様が決まっていないから」で、Issueでも4年間くらい議論され続けています。

空行を最低限にするgoimportsを作った

こういう状況ならオレオレgoimportsを自作するしかないだろうと思って作ってみました。

どれだけ空行を入れていようが順番をバラバラにしていようが、問答無用で空行の数を最低限にしちゃいます。
つまり標準パッケージとそれ以外の間に空行を1つだけ入れます(本家と同じく-localオプションを使えばプロジェクト内のパッケージの前にも空行を入れることができます)。
importブロック内にコメントを書いていた場合も問答無用で削除しちゃいます(これはいい感じの仕様があれば改善したいなと思っています)。

作り方

今回作ったgosimportsは本家のgoimportsのコードを改変する形で実装しました。

goimportsの処理は大きく分けると3つ

1. importするパッケージを整理する

*.goファイルの中身を見て使用しているパッケージを静的解析しています。
本家のソースコードでいうと このあたり ですね。
今回の自作ツールではここはまったく改変していません。

2. グループごとに空行を入れる(gosimportsはここを変えた)

gofmtimportブロック内の空行で区切られたパッケージ群をそれぞれAlphabetical orderにソートします。
なので本家goimportsは出来るだけキレイにするために(?)、標準パッケージとそれ以外の間に空行を入れる処理をgofmtの前段に行っています。
ソースコードでいうと このあたり ですね。

3. gofmtを実行する

最後にgofmtをかけて終了です。

2番の処理を改変した

今回自作したほうのgosimportsでは、↑の2番の処理を改変しています。
ソースコードでいうと このあたり です。
本家の方にあったaddImportSpaces関数をseparateImportsIntoGroups関数に変更しています。
importのグループ分け(標準パッケージとそれ以外とか)はすでに本家のほうに 分類機能 が実装されていたのでそれを使いまわしました。

余談

派生OSSはオリジナルのライセンスを保持する

Goのツール群は多分だいたいBSDライセンスというライセンスのもとでOSSとして公開されていて、goimportsもそうです。
BSDライセンスで公開されているプロジェクトは、ライセンスをそのまま保持しつつオリジナル著者の名前をプロモーションとかで使ったりすることをしなければ、自由にソースを改変・再配布してもオーケーです。
今回作ったgosimportsでもオリジナルのライセンスをそのまま保持しています。 OSS最高。

コード生成などにgofmtは特に便利

今回作ったgosimportsはコード生成をしている部分がかなり適当に作っています。
タブ文字がなかったり無駄な空行とかが入りまくったりしています。
でもその後段で実行しているgofmtがすべてをキレイにしてくれるので、実装するのがとても楽でした。
普段からgofmtにはお世話になっていますが、コード生成とかを実装するときには特に便利だなと思いました。

Renovateとrelease-drafterがとても便利

gosimportsが依存しているパッケージはgolang.org/x/modgolang.org/x/toolsだけですが、golang.org/x/toolsはgitのタグが切られておらずリリースは最新のコミットハッシュが更新されるだけです。
Renovate はタグのリリースだけではなくコミットハッシュの更新にも対応しているので、ちゃんと最新に追従することができます。
あと、masterブランチにPRがマージされるたびにリリースノートに追記してくれる release-drafter もとても便利でした。

gosimportsという命名は割と気に入っている

gosimports = simpler + goimportsです。
結構気に入っています。