Open13

Go の学習がてら actionlint のコードを読んでみる

雪猫雪猫

rhysd/actionlint

GitHub Actions のワークフロー Linter です。
公式提供はされていないので今のところこれくらいしかないはず。

https://github.com/rhysd/actionlint

注意事項

Go は公式チュートリアルをちらっと眺めたくらいでコードをまともに読むのは初めてなので調べながら読んでいきます。
間違いがあったらご指摘ください。

雪猫雪猫

起点

ルート直下にずらっとファイルが並んでいるので起点を探します。
*.go がソースファイルで go.mod や go.sum は Go 言語のパッケージ管理システムですかね。

CONTRIBUTING.md にビルドの方法が記載されているようです。

https://github.com/rhysd/actionlint/blob/a44977690596ce6121e51bdba24766ebc558a1e7/CONTRIBUTING.md#L8

cmd/actionlint ディレクトリをビルドしているようなので main.go を見てみます。
起点はここのようですね。

https://github.com/rhysd/actionlint/blob/a44977690596ce6121e51bdba24766ebc558a1e7/cmd/actionlint/main.go

雪猫雪猫

閑話:エディタ

Visual Studio Code に Go の拡張機能を入れて使用しています。
関数へもジャンプしてくれるのでコードを読む分には問題なさそうです。

https://marketplace.visualstudio.com/items?itemName=golang.Go

GitHub のオンラインエディタでも関数ジャンプくらいはしてくれるのでそちらでも良さそうです。

GoLand でも開いてみたんですがコードをうまく認識してくれていないのか赤線だらけになってしまいました。
再度試してみたら開けました。

雪猫雪猫

actionlint 本体

cmd/actionlint は actionlint.exe 生成用のパッケージだったようなので github.com/rhysd/actionlint パッケージを追ってみましょう。
パッケージという単位をまだよく分かってないですがこの単位でまとまってる雰囲気?

cmd.Main へジャンプすると command.go へ飛ばされます。

https://github.com/rhysd/actionlint/blob/a44977690596ce6121e51bdba24766ebc558a1e7/command.go#L123

flags 変数でフラグを処理していたり opts 変数でオプションを管理していたりするようですが読み飛ばしていくと cmd.runLinter が Linter の処理のようです。

https://github.com/rhysd/actionlint/blob/a44977690596ce6121e51bdba24766ebc558a1e7/command.go#L181

雪猫雪猫

Linter

https://github.com/rhysd/actionlint/blob/a44977690596ce6121e51bdba24766ebc558a1e7/command.go#L81

NewLinter 関数で作られた Linter クラス(?)が引数などに応じて処理を分岐しているようです。
今のところ func の構文すらよく分かっていませんがそのうち調べます。(何で ( ) が 3 つもあるの?w)

https://github.com/rhysd/actionlint/blob/a44977690596ce6121e51bdba24766ebc558a1e7/command.go#L82

.github/actionlint.yaml の生成

https://github.com/rhysd/actionlint/blob/a44977690596ce6121e51bdba24766ebc558a1e7/command.go#L87-L89
https://github.com/rhysd/actionlint/blob/a44977690596ce6121e51bdba24766ebc558a1e7/linter.go#L195-L197

.github/workflows/ 配下を Lint

https://github.com/rhysd/actionlint/blob/a44977690596ce6121e51bdba24766ebc558a1e7/command.go#L91-L93
https://github.com/rhysd/actionlint/blob/a44977690596ce6121e51bdba24766ebc558a1e7/linter.go#L218-L221
コメントが typo してるようだったので PR を送りました。

ファイルパスを抽出して LintFiles へ渡しているようですね。

https://github.com/rhysd/actionlint/blob/a44977690596ce6121e51bdba24766ebc558a1e7/linter.go#L256

標準入力から渡された文字列を Lint

https://github.com/rhysd/actionlint/blob/a44977690596ce6121e51bdba24766ebc558a1e7/command.go#L104
https://github.com/rhysd/actionlint/blob/a44977690596ce6121e51bdba24766ebc558a1e7/linter.go#L401-L406

渡されたファイルパスを Lint

https://github.com/rhysd/actionlint/blob/a44977690596ce6121e51bdba24766ebc558a1e7/command.go#L107
https://github.com/rhysd/actionlint/blob/a44977690596ce6121e51bdba24766ebc558a1e7/linter.go#L259-L262

1 ファイルだけのときは LintFile 関数を呼んでいるようです。
https://github.com/rhysd/actionlint/blob/a44977690596ce6121e51bdba24766ebc558a1e7/linter.go#L268
https://github.com/rhysd/actionlint/blob/a44977690596ce6121e51bdba24766ebc558a1e7/linter.go#L365-L367

雪猫雪猫

Linter.check

Lint, LintFiles, LintFile のいずれでも l.check を呼び出しているのでここが実際のチェック処理でしょうか。

https://github.com/rhysd/actionlint/blob/a44977690596ce6121e51bdba24766ebc558a1e7/linter.go#L409
https://github.com/rhysd/actionlint/blob/a44977690596ce6121e51bdba24766ebc558a1e7/linter.go#L319
https://github.com/rhysd/actionlint/blob/a44977690596ce6121e51bdba24766ebc558a1e7/linter.go#L387

この関数は並列で呼び出されるのでスレッドセーフではないといけない旨が書かれていますね。

https://github.com/rhysd/actionlint/blob/a44977690596ce6121e51bdba24766ebc558a1e7/linter.go#L422-L424

雪猫雪猫

感想

ここまでが全体の流れ。
初見でもなんとなく読めるくらい読みやすいですね。
後は個別のルールを見ていけば全部把握できそうです。

ただ 1 文字変数は(Go の慣習なのかもしれませんが)画面外までたどらないといけなくてリーダブルコードのスコープが十分に短ければ良いというのに反しているように思えました。(中身なんだっけ?というのを何回かやったし、この記事のように引用したときに読めない)
err や fmt のような短縮も読みづらいと感じました。

Go 学習

調べながら読んでいきますとか書いてたけど全然調べなかった。
なんとなく覚えたこと。

  • モジュールは package
  • package の読み込みは import()
  • 変数は :=
  • 関数は func
  • クラスは type Linter struct {}
  • null は nil
  • if は if err != nil {}
  • 配列は rules := []Rule{}
  • foreach は for _, rule := range rules {}
  • _ は受け取るけど不使用
  • enum は const ()
雪猫雪猫

1 文字変数はエディタの Sticky Scroll を有効にすることで読めるようになりそう。