r := strconv.Atoi(splitted[0])
if err != nil {
return 0, 0, err
}
end := start
if len(splitted) == 2 {
end, err = strconv.Atoi(splitted[1])
if err != nil {
return 0, 0, err
}
}
if start > end {
return 0, 0, errors.New("wrong range. start line cannot be larger than end line")
}
return start, end, nil
}
当您选中一组行并高亮它们时,编辑器将使用此模式。
偏移量和结构体名称选择需要做更多的工作。 对于这些,我们首先需要收集所有给定的结构体,以便可以计算偏移位置或搜索结构体名称。为此,我们首先要有一个收集所有结构体的函数:
// collectStructs collects and maps structType nodes to their positions
func collectStructs(node ast.Node) map[token.Pos]*structType {
structs := make(map[token.Pos]*structType, 0)
collectStructs := func(n ast.Node) bool {
t, ok := n.(*ast.TypeSpec)
if !ok {
return true
}
if t.Type == nil {
return true
}
structName := t.Name.Name
x, ok := t.Type.(*ast.StructType)
if !ok {
return true
}
structs[x.Pos()] = &structType{
name: structName,
node: x,
}
return true
}
ast.Inspect(node, collectStructs)
return structs
}
我们使用 ast.Inspect()
函数逐步遍历 AST 并搜索结构体。
我们首先搜索 *ast.TypeSpec
,以便我们可以获得结构体名称。搜索 *ast.StructType
时给定的是结构体本身,而不是它的名字。 这就是为什么我们有一个自定义的 structType
类型,它保存了名称和结构体节点本身。这样在各个地方都很方便。 因为每个结构体的位置都是唯一的,并且在同一位置上不可能存在两个不同的结构体,因此我们使用位置作为 map 的键。
现在我们拥有了所有结构体,在最后可以返回一个结构体的起始位置和结束位置的偏移量和结构体名称模式。 对于偏移位置,我们检查偏移是否在给定的结构体之间:
func (c *config) offsetSelection(file ast.Node) (int, int, error) {
structs := collectStructs(file)
var encStruct *ast.StructType
for _, st := range structs {
structBegin := c.fset.Position(st.node.Pos()).Offset
structEnd := c.fset.Position(st.node.End()).Offset
if structBegin <= c.offset && c.offset <= structEnd {
encStruct = st.node
break
}
}
if encStruct == nil {
return 0, 0, errors.New("offset is not inside a struct")
}
// offset mode selects all fields
start := c.fset.Position(encStruct.Pos()).Line
end := c.fset.Position(encStruct.End()).Line
return start, end, nil
}
我们使用 collectStructs()
来收集所有结构体,之后在这里迭代。还得记得我们存储了用于解析文件的初始 token.FileSet
么?
现在可以用它来获取每个结构体节点的偏移信息(我们将其解码为一个 token.Position
,它为我们提供了 .Offset
字段)。 我们所做的只是一个简单的检查和迭代,直到我们找到结构体(这里命名为 encStruct
):
for _, st := range structs {
structBegin := c.fset.Position(st.node.Pos()).Offset
structEnd := c.fset.Position(st.node.End()).Offset
if structBegin <= c.offset && c.offset <= structEnd {
encStruct = st.node
break
}
}
有了这些信息,我们可以提取找到的结构体的开始位置和结束位置:
start := c.fset.Position(encStruct.Pos()).Line
end := c.fset.Position(encStruct.End()).Line
该逻辑同样适用于结构体名称选择。 我们所做的只是尝试检查结构体名称,直到找到与给定名称一致的结构体,而不是检查偏移量是否在给定的结构体范围内:
func (c *config) structSelection(file ast.Node) (int, int, error) {
// ...
for _, st := range structs {
if st.name == c.structName {
encStruct = st.node
}
}
// ...
}
现在我们有了开始位置和结束位置,我们终于可以进行第三步了:修改结构体字段。
在 main
函数中,我们将使用从上一步解析的节点来调用 cfg.rewrite()
函数:
func main() {
// ... find start and end position of the struct to be modified
rewrittenNode, errs := cfg.rewrite(node, start, end)
if errs != nil {
if _, ok := errs.(*rewriteErrors); !ok {
return errs
}
}
// continue outputting the rewritten node
}
这是该工具的核心。在 rewrite
函数中,我们将重写开始位置和结束位置之间的所有结构体字段。 在深入了解之前,我们可以看一下该函数的大概内容:
// rewrite rewrites the node for structs between the start and end
// positions and returns the rewritten node
func (c *config) rewrite(node ast.Node, start, end int) (ast.Node, error) {
errs := &rewriteErrors{err