parsing go abstract-syntax-tree

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!