s: make([]error, 0)}
rewriteFunc := func(n ast.Node) bool {
// rewrite the node ...
}
if len(errs.errs) == 0 {
return node, nil
}
ast.Inspect(node, rewriteFunc)
return node, errs
}
正如你所看到的,我们再次使用 ast.Inspect()
来逐步处理给定节点的树。我们重写 rewriteFunc
函数中的每个字段的标签(更多内容在后面)。
因为传递给 ast.Inspect()
的函数不会返回错误,因此我们将创建一个错误映射(使用 errs
变量定义),之后在我们逐步遍历树并处理每个单独的字段时收集错误。现在让我们来谈谈 rewriteFunc
的内部原理:
rewriteFunc := func(n ast.Node) bool {
x, ok := n.(*ast.StructType)
if !ok {
return true
}
for _, f := range x.Fields.List {
line := c.fset.Position(f.Pos()).Line
if !(start <= line && line <= end) {
continue
}
if f.Tag == nil {
f.Tag = &ast.BasicLit{}
}
fieldName := ""
if len(f.Names) != 0 {
fieldName = f.Names[0].Name
}
// anonymous field
if f.Names == nil {
ident, ok := f.Type.(*ast.Ident)
if !ok {
continue
}
fieldName = ident.Name
}
res, err := c.process(fieldName, f.Tag.Value)
if err != nil {
errs.Append(fmt.Errorf("%s:%d:%d:%s",
c.fset.Position(f.Pos()).Filename,
c.fset.Position(f.Pos()).Line,
c.fset.Position(f.Pos()).Column,
err))
continue
}
f.Tag.Value = res
}
return true
}
记住,AST 树中的每一个节点都会调用这个函数。因此,我们只寻找类型为 *ast.StructType
的节点。一旦我们拥有,就可以开始迭代结构体字段。
这里我们使用 start
和 end
变量。这定义了我们是否要修改该字段。如果字段位置位于 start-end 之间,我们将继续,否则我们将忽略:
if !(start <= line && line <= end) {
continue // skip processing the field
}
接下来,我们检查是否存在标签。如果标签字段为空(也就是 nil),则初始化标签字段。这在有助于后面的 cfg.process()
函数避免 panic:
if f.Tag == nil {
f.Tag = &ast.BasicLit{}
}
现在让我先解释一下一个有趣的地方,然后再继续。gomodifytags 尝试获取字段的字段名称并处理它。然而,当它是一个匿名字段呢?:
type Bar string
type Foo struct {
Bar //this is an anonymous field
}
在这种情况下,因为没有字段名称,我们尝试从类型名称中获取字段名称:
// if there is a field name use it
fieldName := ""
if len(f.Names) != 0 {
fieldName = f.Names[0].Name
}
// if there is no field name, get it from type's name
if f.Names == nil {
ident, ok := f.Type.(*ast.Ident)
if !ok {
continue
}
fieldName = ident.Name
}
一旦我们获得了字段名称和标签值,就可以开始处理该字段。cfg.process()
函数负责处理有字段名称和标签值(如果有的话)的字段。在它返回处理结果后(在我们的例子中是 struct tag 格式),我们使用它来覆盖现有的标签值:
res, err := c.process(fieldName, f.Tag.Value)
if err != nil {
errs.Append(fmt.Errorf("%s:%d:%d:%s",
c.fset.Position(f.Pos()).Filename,
c.fset.Position(f.Pos()).Line,
c.fset.Position(f.Pos()).Column,
err))
continue
}
// rewrite the field with the new result,i.e: json:"foo"
f.Tag.Value = res
实际上,如果你记得 structtag,它返回标签实例的 String() 表述。在我们返回标签的最终表述之前,我们根据需要使用 structtag 包的各种方法修改结构体。以下是一个简单的说明图示:
例如,我们要扩展 process() 中的 removeTags()
函数。此功能使用以下配置来创建要删除的标签数组(键名称):
flagRemoveTags = flag.String("remove-tags", "", "Remove tags for the comma separated list of keys")
if *flagRemoveTags != "" {
cfg.remove = strings.Split(*flagRemoveTags, ",")
}
在 removeTags()
中,我们检查是否使用了 --remove-tags
。如果有,我们将使用 structtag 的 tags.Delete() 方法来删除标签:
func (c *config) removeTags(tags *structtag.Tags) *structtag.Tags {
if c.remove == nil || len(c.remove) == 0 {
return tags
}
tags.Delete(c.remove...)
return tags
}
此逻辑同样适用于 cfg.Process()
中的所有函数。
我们已经有了一个重写的节点,让我们来讨论最后一个话题。输出和格式化结果:
在 main 函数中,我们将使用上一步重写的节点来调用 cfg.format()
函数:
func main() {
// ... rewrite the node
out, err := cfg.format(rewrittenNode, errs)
if err != nil {
return err
}
fmt.Println(out)
}
您需要注意的一