KDOC 23: unusedを読む
unusedは使われてないパッケージの識別子を探すGoツールである。
memo
検知部分
https://github.com/gostaticanalysis/unused/blob/17bc41fa0d5fdae5bb1a28f83236fa0dc574b666/unused.go#L23-L32
func run(pass *analysis.Pass) (interface{}, error) {
m := pass.ResultOf[ident.Analyzer].(ident.Map)
for o := range m {
if !skip(o) && len(m[o]) = 1 {
n :
m[o][0]
pass.Reportf(n.Pos(), “%s is unused”, n.Name)
}
}
return nil, nil
}
- mにはmapで識別子が入っている
- どこか別で呼び出されるとlen(m[o])は1より大きくなる
- 一度しか出現してないと、スライスの長さは1になる(定義の1回分)
- m[o]には*ast.Identの配列が入ってる
- skip()はいったんおいておいて、ロジックは明快
- 識別子の数が1なら定義でしか出てきてないので、呼び出されてないことになる
skip関数
本質的な関数。
https://github.com/gostaticanalysis/unused/blob/17bc41fa0d5fdae5bb1a28f83236fa0dc574b666/unused.go#L36-L38
if o = nil || o.Parent() =
types.Universe || o.Exported() {
return true
}
- なんで代入なんだろう
- objectがnilであれスキップ
- parentがUniverseスコープ…つまり組み込みの識別子であればスキップ
- 公開した識別子であればスキップ。パッケージ外で使われてる可能性があるから
https://github.com/gostaticanalysis/unused/blob/17bc41fa0d5fdae5bb1a28f83236fa0dc574b666/unused.go#L41-L42
case *types.PkgName: return true
- types.Objectで分岐する
- パッケージ名の場合
- 飛ばす
- パッケージ名は明らかに1回しか出てこないので、まあわかる
- 変数の場合
- 特定のケースでスキップ
- types.Varではvarがくるときとfieldが来るときがある。フィールドを初期化するときのものか
- オブジェクトのフィールド、無名関数、などが関係する
- 関数の場合
- object名がmainかつパッケージ名がmainのときはスキップ
- mainパッケージのmain()は呼び出しはない
- object名がinitかつスコープが親と同じ場合スキップ
- メソッドだと、インターフェース内の関数を実装していれば使っているということになる
- インターフェース定義の中に来るのは関数
- object名がmainかつパッケージ名がmainのときはスキップ
- パッケージ名の場合
- o.Parent()ってなんだろう。
- オブジェクトの名前と、オブジェクトが属するパッケージの名前がある。
https://github.com/gostaticanalysis/unused/blob/17bc41fa0d5fdae5bb1a28f83236fa0dc574b666/unused.go#L43-L47
case *types.Var: if o.Pkg().Scope() != o.Parent() && !(o.IsField() && !o.Anonymous() && isFieldInNamedStruct(o)) { return true }
- scopeの知識が必要そう
- scopeは木構造で、parentが取れる
- 一致しなければスルー(skip) … trueを返す可能性
- (フィールドであるかつ、無名関数ではないかつ、構造体の名前付きフィールドである(不明))…ではない
- 一致してればskipしない … falseを返す
- 2回目として出てきたなら、parentが取れるはずで、一致しないだろう。なのでskip