17

Go のパーサーastパッケージを使用して、構造体型に関連付けられた Doc コメントを読み取ろうとしています。この例では、コードは単にそれ自体をソースとして使用しています。

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
        })
    }
}

func と fields のコメント ドキュメントは問題なく出力されますが、何らかの理由で「FirstType ドキュメント」と「SecondType ドキュメント」がどこにも見つかりません。私は何が欠けていますか?Go のバージョンは 1.1.2 です。

(上記を実行するには、それを main.go ファイルに保存してからgo run main.go)

4

3 に答える 3

19

素晴らしい質問です!

のソース コードを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です。幸運を!

于 2016-02-18T16:36:47.967 に答える
10

go/docast からドキュメントを抽出するには、パッケージを使用する必要があります。

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)
        }
    }
}
于 2013-10-25T03:50:55.123 に答える