素晴らしい質問です!
のソース コードをgo/doc
見ると、関数内でこれと同じケースを処理する必要があることがわかりreadType
ます。そこには、次のように書かれています。
324 func (r *reader) readType(decl *ast.GenDecl, spec *ast.TypeSpec) {
...
334 // compute documentation
335 doc := spec.Doc
336 spec.Doc = nil // doc consumed - remove from AST
337 if doc == nil {
338 // no doc associated with the spec, use the declaration doc, if any
339 doc = decl.Doc
340 }
...
特に、AST に TypeSpec に添付されたドキュメントがない場合にどのように対処する必要があるかに注意してください。これを行うために、 にフォールバックしますGenDecl
。これにより、AST を直接使用して構造体のドキュメント コメントを解析する方法の手がかりが得られます。質問コードの for ループを適応させて、 for のケースを追加します*ast.GenDecl
。
for _, f := range d {
ast.Inspect(f, func(n ast.Node) bool {
switch x := n.(type) {
case *ast.FuncDecl:
fmt.Printf("%s:\tFuncDecl %s\t%s\n", fset.Position(n.Pos()), x.Name, x.Doc.Text())
case *ast.TypeSpec:
fmt.Printf("%s:\tTypeSpec %s\t%s\n", fset.Position(n.Pos()), x.Name, x.Doc.Text())
case *ast.Field:
fmt.Printf("%s:\tField %s\t%s\n", fset.Position(n.Pos()), x.Names, x.Doc.Text())
case *ast.GenDecl:
fmt.Printf("%s:\tGenDecl %s\n", fset.Position(n.Pos()), x.Doc.Text())
}
return true
})
}
これを実行すると、次のようになります。
main.go:3:1: GenDecl %!s(*ast.CommentGroup=<nil>)
main.go:11:1: GenDecl &{[%!s(*ast.Comment=&{69 // FirstType docs})]}
main.go:11:6: TypeSpec FirstType %!s(*ast.CommentGroup=<nil>)
main.go:13:2: Field [FirstMember] &{[%!s(*ast.Comment=&{112 // FirstMember docs})]}
main.go:17:1: GenDecl &{[%!s(*ast.Comment=&{155 // SecondType docs})]}
main.go:17:6: TypeSpec SecondType %!s(*ast.CommentGroup=<nil>)
main.go:19:2: Field [SecondMember] &{[%!s(*ast.Comment=&{200 // SecondMember docs})]}
main.go:23:1: FuncDecl main &{[%!s(*ast.Comment=&{245 // Main docs})]}
main.go:33:23: Field [n] %!s(*ast.CommentGroup=<nil>)
main.go:33:35: Field [] %!s(*ast.CommentGroup=<nil>)
そして、ねえ!
FirstType docs
長い間失われていたand SecondType docs
!を印刷しました。しかし、これでは不十分です。ドキュメントが に添付されていないのはなぜTypeSpec
ですか? 構造体宣言に関連するドキュメントがない場合、ファイルはこの問題を回避するために非常に長くなり、実際に偽物を生成して前述の関数にgo/doc/reader.go
渡しGenDecl
ます!readType
503 fake := &ast.GenDecl{
504 Doc: d.Doc,
505 // don't use the existing TokPos because it
506 // will lead to the wrong selection range for
507 // the fake declaration if there are more
508 // than one type in the group (this affects
509 // src/cmd/godoc/godoc.go's posLink_urlFunc)
510 TokPos: s.Pos(),
511 Tok: token.TYPE,
512 Specs: []ast.Spec{s},
513 }
しかし、なぜこれがすべてですか?
質問のコードから型定義を少し変更したと想像してください (このような構造体を定義することは一般的ではありませんが、それでも有効な Go です)。
// This documents FirstType and SecondType together
type (
// FirstType docs
FirstType struct {
// FirstMember docs
FirstMember string
}
// SecondType docs
SecondType struct {
// SecondMember docs
SecondMember string
}
)
コードを実行すると ( のケースを含むast.GenDecl
)、次のようになります。
main.go:3:1: GenDecl %!s(*ast.CommentGroup=<nil>)
main.go:11:1: GenDecl &{[%!s(*ast.Comment=&{69 // This documents FirstType and SecondType together})]}
main.go:13:2: TypeSpec FirstType &{[%!s(*ast.Comment=&{129 // FirstType docs})]}
main.go:15:3: Field [FirstMember] &{[%!s(*ast.Comment=&{169 // FirstMember docs})]}
main.go:19:2: TypeSpec SecondType &{[%!s(*ast.Comment=&{215 // SecondType docs})]}
main.go:21:3: Field [SecondMember] &{[%!s(*ast.Comment=&{257 // SecondMember docs})]}
main.go:26:1: FuncDecl main &{[%!s(*ast.Comment=&{306 // Main docs})]}
main.go:36:23: Field [n] %!s(*ast.CommentGroup=<nil>)
main.go:36:35: Field [] %!s(*ast.CommentGroup=<nil>)
それは正しい
現在、構造体型の定義にはドキュメントがあり、GenDecl
にも独自のドキュメントがあります。質問に投稿された最初のケースでは、ドキュメントはに添付されGenDecl
ていました。AST は、型定義の括弧付きバージョンの「縮約」の個々の構造体型定義を見て、すべての定義を同じように処理したいためです。グループ化されているかどうか。次のように、変数定義でも同じことが起こります。
// some general docs
var (
// v docs
v int
// v2 docs
v2 string
)
したがって、純粋な AST でコメントを解析したい場合は、これがどのように機能するかを認識する必要があります。しかし、@mjibsonが提案したように、推奨される方法はを使用することgo/doc
です。幸運を!