parsing - El analizador no detecta los comentarios de Doc en el tipo de estructura
go abstract-syntax-tree (2)
Intento leer los comentarios de Doc asociados en un tipo de estructura usando el analizador de Go y los paquetes ast . En este ejemplo, el código simplemente se usa a sí mismo como fuente.
package main
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
)
// FirstType docs
type FirstType struct {
// FirstMember docs
FirstMember string
}
// SecondType docs
type SecondType struct {
// SecondMember docs
SecondMember string
}
// Main docs
func main() {
fset := token.NewFileSet() // positions are relative to fset
d, err := parser.ParseDir(fset, "./", nil, parser.ParseComments)
if err != nil {
fmt.Println(err)
return
}
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)
case *ast.TypeSpec:
fmt.Printf("%s:/tTypeSpec %s/t%s/n", fset.Position(n.Pos()), x.Name, x.Doc)
case *ast.Field:
fmt.Printf("%s:/tField %s/t%s/n", fset.Position(n.Pos()), x.Names, x.Doc)
}
return true
})
}
}
Los documentos de comentarios para el func y los campos no tienen ningún problema, pero por alguna razón los ''FirstType docs'' y ''SecondType docs'' no se encuentran por ningún lado. ¿Qué me estoy perdiendo? La versión Go es 1.1.2.
(Para ejecutar lo anterior, guárdelo en un archivo main.go y go run main.go
)
Debe usar el paquete go/doc
para extraer la documentación del ast:
package main
import (
"fmt"
"go/doc"
"go/parser"
"go/token"
)
// FirstType docs
type FirstType struct {
// FirstMember docs
FirstMember string
}
// SecondType docs
type SecondType struct {
// SecondMember docs
SecondMember string
}
// Main docs
func main() {
fset := token.NewFileSet() // positions are relative to fset
d, err := parser.ParseDir(fset, "./", nil, parser.ParseComments)
if err != nil {
fmt.Println(err)
return
}
for k, f := range d {
fmt.Println("package", k)
p := doc.New(f, "./", 0)
for _, t := range p.Types {
fmt.Println(" type", t.Name)
fmt.Println(" docs:", t.Doc)
}
}
}
Gran pregunta!
Al observar el código fuente de go/doc
, podemos ver que tiene que tratar con este mismo caso en la función readType
. Ahí, dice:
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 }
...
Observe en particular cómo se debe tratar el caso donde el AST no tiene un documento adjunto al TypeSpec. Para hacer esto, GenDecl
. Esto nos da una pista sobre cómo podemos usar el AST directamente para analizar los comentarios del doc para las estructuras. Adaptar el bucle for en el código de pregunta para agregar un caso para *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
})
}
Ejecutar esto nos da:
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>)
Y, oye!
¡Hemos impreso los FirstType docs
perdidos y los SecondType docs
! Pero esto no es satisfactorio. ¿Por qué el documento no está adjunto a TypeSpec
? El archivo go/doc/reader.go
llega a extremos extraordinarios para eludir este problema, en realidad genera un GenDecl
falso y lo pasa a la función readType
mencionada anteriormente, ¡si no hay documentación asociada con la declaración struct!
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 }
Pero ¿por qué todo esto?
Imagina que cambiamos las definiciones de tipo de código en la pregunta ligeramente (la definición de estructuras como esta no es común, pero sigue siendo válida Go):
// This documents FirstType and SecondType together
type (
// FirstType docs
FirstType struct {
// FirstMember docs
FirstMember string
}
// SecondType docs
SecondType struct {
// SecondMember docs
SecondMember string
}
)
Ejecute el código (incluido el caso de ast.GenDecl
) y obtenemos:
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>)
Está bien
Ahora las definiciones de tipo de estructura tienen sus documentos, y GenDecl
tiene su propia documentación. En el primer caso, publicado en la pregunta, el documento se adjuntó a GenDecl
, ya que AST ve las definiciones de tipo de estructura individual de "contracciones" de la versión entre paréntesis de definiciones de tipo, y quiere manejar todas las definiciones de la misma manera, ya sea están agrupados o no Lo mismo sucedería con las definiciones de variables, como en:
// some general docs
var (
// v docs
v int
// v2 docs
v2 string
)
Entonces, si desea analizar los comentarios con AST puro, debe tener en cuenta que así es como funciona. Pero el método preferido, como sugirió @mjibson, es usar go/doc
. ¡Buena suerte!