好问题!
查看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循环适应于*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
和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
。祝好运!
TypeSpec
)上提供注释/文档的问题。 - themihai