Goでちょっとしたウェブアプリケーションを書く際、依存を減らしたい気持ちでGoのhtml/templateを使う場面もないわけではない。とはいえ、HTMLを組み立てるのも最近はReact/TypeScriptに慣れきってしまっているので、実行してみるまでテンプレートが正しいかどうかわからない、なんてのは不安を誘う状況だ。
しかし、テンプレートの解析時に {{template ...}}
で呼び出されるテンプレートを指定しないとエラーとなることからもわかるとおり、Goのテンプレートは事前にしっかり解析されているらしい。そしてありがたいことに、Goのテンプレートはそのパーザを別パッケージとして公開してくれている(text/template/parse)。これを使って、Goのテンプレートを静的型チェックするツールを書いた。
GitHub - motemen/go-template-statictools
というわけでさっそくインストール。
$ go install github.com/motemen/go-template-statictools/cmd/gotmplcheck@latest
使い方は簡単。チェックしたいテンプレートファイルを引数に渡すだけ。
$ gotmplcheck template.html
とはいえこれだけだと構文解析が通るかどうかだけであまり意味がない。型情報が渡されない場合はいわゆるanyのように扱われて、すべての型チェックが通ってしまう。
実際には、テンプレートに渡すデータの型と、それに対するフィールドやメソッドの呼び出しが正しいかどうかを知りたい。-dot
でこの型を指定できる。exampleディレクトリの例だと、
$ gotmplcheck -dot github.com/motemen/go-template-statictools/example.Data example/example.tmpl
という感じ。するとこのようにエラーが表示される。やったね!
example/example.tmpl:4:11: in .Title_with_typo: can't evaluate field Title_with_typo in type github.com/motemen/go-template-statictools/example.Meta
example/example.tmpl:10:9: in .NoSuchMethod: can't evaluate field NoSuchMethod in type github.com/motemen/go-template-statictools/example.Item
example/example.tmpl:12:7: in $v.NoSuchField: can't evaluate field NoSuchField in type string
データの型の指定はテンプレート中に {{/* @type github.com/motemen/go-template-statictools/example.Data */}}
などと書くことでも可能。
レイアウトファイルのようなものを用意してテンプレートを複数分けている場合は、それらをまとめて指定する。最初のファイルがエントリポイントになる。
$ gotmplcheck example/layout_layout.tmpl example/layout_content.tmpl
ユーザ定義の関数(FuncMap)がグローバル変数として定義されていれば、それをオプションで指定することも可能。これはちょっと使いにくそう。
$ gotmplcheck -funcmap github.com/motemen/go-template-statictools/example.funcs example/example.tmpl
ユーザ定義の関数や呼び出すテンプレートが存在しない場合もエラーにしたくない場合は -soft
を指定することで無視できる。
とまあ書いてみて、それなりに使えるものにはなった。実装漏れなどで途中で型がわからなくなった場合は、それ以降の型チェックを無言で通してしまうので、完全な型チェックを行えるようなクオリティではないけど、まあまあ便利かな。