为了账号安全,请及时绑定邮箱和手机立即绑定

将项目添加到 Go AST 后评论乱序

将项目添加到 Go AST 后评论乱序

Go
慕妹3242003 2021-10-25 16:53:36
以下测试尝试使用 AST 向结构添加字段。字段添加正确,但注释添加顺序不正确。我认为可能需要手动指定位置,但到目前为止我已经找到了答案。这是一个失败的测试:http : //play.golang.org/p/RID4N30FZK这是代码:package generatorimport (    "bytes"    "fmt"    "go/ast"    "go/parser"    "go/printer"    "go/token"    "testing")func TestAst(t *testing.T) {    source := `package a// B commenttype B struct {    // C comment    C string}`    fset := token.NewFileSet()    file, err := parser.ParseFile(fset, "", []byte(source), parser.ParseComments)    if err != nil {        t.Error(err)    }    v := &visitor{        file: file,    }    ast.Walk(v, file)    var output []byte    buf := bytes.NewBuffer(output)    if err := printer.Fprint(buf, fset, file); err != nil {        t.Error(err)    }    expected := `package a// B commenttype B struct {    // C comment    C string    // D comment    D int    // E comment    E float64}`    if buf.String() != expected {        t.Error(fmt.Sprintf("Test failed. Expected:\n%s\nGot:\n%s", expected, buf.String()))    }    /*    actual output = `package a// B commenttype B struct {    // C comment    // D comment    // E comment    C   string    D   int    E   float64}`    */}type visitor struct {    file *ast.File}func (v *visitor) Visit(node ast.Node) (w ast.Visitor) {    if node == nil {        return v    }    switch n := node.(type) {    case *ast.GenDecl:        if n.Tok != token.TYPE {            break        }        ts := n.Specs[0].(*ast.TypeSpec)        if ts.Name.Name == "B" {            fields := ts.Type.(*ast.StructType).Fields            addStructField(fields, v.file, "int", "D", "D comment")            addStructField(fields, v.file, "float64", "E", "E comment")        }    }    return v}
查看完整描述

2 回答

?
明月笑刀无情

TA贡献1828条经验 获得超4个赞

我相信我已经让它发挥作用了。正如我在上面的评论中所述,所需的要点是:

  1. 具体设置缓冲区位置包括SlashNamePos

  2. 用于token.File.AddLine在特定偏移处添加新行(使用第 1 项中的位置计算)

  3. 过度分配源缓冲区token.File.Position(由源缓冲区使用,printer.Printer并且token.File.Addline不会使源缓冲区的范围检查失败

代码:

package main


import (

    "bytes"

    "fmt"

    "go/ast"

    "go/parser"

    "go/printer"

    "go/token"

    "testing"

)


func main() {

    tests := []testing.InternalTest{{"TestAst", TestAst}}

    matchAll := func(t string, pat string) (bool, error) { return true, nil }

    testing.Main(matchAll, tests, nil, nil)

}


func TestAst(t *testing.T) {


    source := `package a


// B comment

type B struct {

    // C comment

    C string

}`


    buffer := make([]byte, 1024, 1024)

    for idx,_ := range buffer {

        buffer[idx] = 0x20

    }

    copy(buffer[:], source)

    fset := token.NewFileSet()

    file, err := parser.ParseFile(fset, "", buffer, parser.ParseComments)

    if err != nil {

        t.Error(err)

    }


    v := &visitor{

        file: file,

        fset: fset,

    }

    ast.Walk(v, file)


    var output []byte

    buf := bytes.NewBuffer(output)

    if err := printer.Fprint(buf, fset, file); err != nil {

        t.Error(err)

    }


    expected := `package a


// B comment

type B struct {

    // C comment

    C   string

    // D comment

    D   int

    // E comment

    E   float64

}

`

    if buf.String() != expected {

        t.Error(fmt.Sprintf("Test failed. Expected:\n%s\nGot:\n%s", expected, buf.String()))

    }


}


type visitor struct {

    file *ast.File

    fset *token.FileSet

}


func (v *visitor) Visit(node ast.Node) (w ast.Visitor) {


    if node == nil {

        return v

    }


    switch n := node.(type) {

    case *ast.GenDecl:

        if n.Tok != token.TYPE {

            break

        }

        ts := n.Specs[0].(*ast.TypeSpec)

        if ts.Name.Name == "B" {

            fields := ts.Type.(*ast.StructType).Fields

            addStructField(v.fset, fields, v.file, "int", "D", "D comment")

            addStructField(v.fset, fields, v.file, "float64", "E", "E comment")

        }

    }


    return v

}


func addStructField(fset *token.FileSet, fields *ast.FieldList, file *ast.File, typ string, name string, comment string) {

    prevField := fields.List[fields.NumFields()-1] 


    c := &ast.Comment{Text: fmt.Sprint("// ", comment), Slash: prevField.End() + 1}

    cg := &ast.CommentGroup{List: []*ast.Comment{c}}

    o := ast.NewObj(ast.Var, name)

    f := &ast.Field{

        Doc:   cg,

        Names: []*ast.Ident{&ast.Ident{Name: name, Obj: o, NamePos: cg.End() + 1}},

    }

    o.Decl = f

    f.Type = &ast.Ident{Name: typ, NamePos: f.Names[0].End() + 1}


    fset.File(c.End()).AddLine(int(c.End()))

    fset.File(f.End()).AddLine(int(f.End()))


    fields.List = append(fields.List, f)

    file.Comments = append(file.Comments, cg)

}

示例:http : //play.golang.org/p/_q1xh3giHm


对于Item (3),将所有过度分配的字节设置为空格 ( 0x20)也很重要,以便打印机在处理它们时不会抱怨空字节。


查看完整回答
反对 回复 2021-10-25
  • 2 回答
  • 0 关注
  • 160 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信