在向Go AST添加项目后,注释顺序错误。

huangapple go评论85阅读模式
英文:

Comments out of order after adding item to Go AST

问题

以下是翻译好的内容:

以下测试尝试使用AST向结构体添加字段。字段被正确添加,但是注释的顺序被打乱了。我猜想可能需要手动指定位置,但是到目前为止我还没有找到答案。

这是一个失败的测试:http://play.golang.org/p/RID4N30FZK

以下是代码:

package generator

import (
	"bytes"
	"fmt"
	"go/ast"
	"go/parser"
	"go/printer"
	"go/token"
	"testing"
)

func TestAst(t *testing.T) {

	source := `package a

// B comment
type 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 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()))
	}

	/*
	actual output = `package a

// B comment
type 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
}

func addStructField(fields *ast.FieldList, file *ast.File, typ string, name string, comment string) {
	c := &ast.Comment{Text: fmt.Sprint("// ", comment)}
	cg := &ast.CommentGroup{List: []*ast.Comment{c}}
	f := &ast.Field{
		Doc:   cg,
		Names: []*ast.Ident{ast.NewIdent(name)},
		Type:  ast.NewIdent(typ),
	}
	fields.List = append(fields.List, f)
	file.Comments = append(file.Comments, cg)
}

希望对你有帮助!

英文:

The following test attempts to use AST to add fields to a struct. The fields are added correctly, but the comments are added out of order. I gather the position may need to be specified manually, but I've so far drawn a blank finding an answer.

Here's a failing test: http://play.golang.org/p/RID4N30FZK

Here's the code:

package generator
import (
"bytes"
"fmt"
"go/ast"
"go/parser"
"go/printer"
"go/token"
"testing"
)
func TestAst(t *testing.T) {
source := `package a
// B comment
type 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 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()))
}
/*
actual output = `package a
// B comment
type 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
}
func addStructField(fields *ast.FieldList, file *ast.File, typ string, name string, comment string) {
c := &ast.Comment{Text: fmt.Sprint("// ", comment)}
cg := &ast.CommentGroup{List: []*ast.Comment{c}}
f := &ast.Field{
Doc:   cg,
Names: []*ast.Ident{ast.NewIdent(name)},
Type:  ast.NewIdent(typ),
}
fields.List = append(fields.List, f)
file.Comments = append(file.Comments, cg)
}

答案1

得分: 7

我相信我已经让它工作了。如上面我的评论所述,需要注意以下主要要点:

  1. 明确设置缓冲区位置,包括“Slash”和“NamePos”。
  2. 使用“token.File.AddLine”在特定偏移量处添加新行(使用第1步中的位置计算)。
  3. 过度分配源缓冲区,以便“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)
}

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

英文:

I believe I have gotten it to work. As stated in my comment above, the main points required are:

  1. Specifically set the buffer locations including the Slash and NamePos
  2. Use token.File.AddLine to add new lines at specific offsets (calculated using the positions from item 1)
  3. Overallocate the source buffer so token.File.Position (used by printer.Printer and token.File.Addline don't fail range checks on the source buffer

Code:

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

Example: http://play.golang.org/p/_q1xh3giHm

For Item (3), it is also important to set all the overallocated bytes to spaces (0x20), so that the printer doesn't complain about null bytes when processing them.

答案2

得分: 2

我知道这个答案可能有点晚。但为了其他人的利益,我在以下GitHub问题中找到了对这个库的参考:

https://github.com/golang/go/issues/20744

这个库叫做dst,它可以将Go的ast转换为dst,反之亦然。

https://github.com/dave/dst

ast中,注释是按照它们的字节偏移存储的,而不是附加到节点上。Dst通过将注释附加到其相应的节点上来解决这个问题,以便重新排列节点不会破坏输出/树。

这个库的功能如广告所述,我到目前为止还没有发现任何问题。

注意:还有一个名为dst/dstutil的子包,与golang.org/x/tools/go/ast/astutil兼容。

英文:

I know that this answer might be a little late. But for the benefit of others, I found a reference to this library in the following GitHub issue

https://github.com/golang/go/issues/20744

The library is called dst and it can convert a go ast to dst and vice versa.

https://github.com/dave/dst

In ast, Comments are stored by their byte offset instead of attached to nodes. Dst solves this by attaching the comments to its respective nodes so that re-arranging nodes doesn't break the output/tree.

The library works as advertized and I haven't found any issues so far.

Note: There is also a subpackage called dst/dstutil which is compatible with golang.org/x/tools/go/ast/astutil

huangapple
  • 本文由 发表于 2015年7月26日 00:25:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/31628613.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定