使用 go ast 从位置获取周围的函数名称
Use go ast to get surrounding function name from position
仅通过源代码(无运行时检查)从文件位置获取周围函数名称的最佳方法是什么?
例如说我有一些代码:
func MyFunc() {
doSomething() // package/file.go:215:15
}
而且我有doSomething的位置,在package/file.go:215:15
,有没有办法轻松获取MyFunc?
包含“最好”一词的问题总是很难回答,因为这在很大程度上取决于您的要求,而您没有说出具体要求。
我想到了 2 种方法来解决这个问题,都有各自的优缺点列表:
快速而肮脏
快速而肮脏的方法是循环遍历每一行代码,并在我们到达我们感兴趣的行之前查找最近出现的函数减速。这应该是包含我们行的函数。
package main
import (
"bufio"
"fmt"
"os"
"regexp"
"strconv"
"strings"
)
func main() {
if len(os.Args) < 2 {
usage()
}
loc := strings.Split(os.Args[1], ":")
if len(loc) != 2 {
usage()
}
filePath := loc[0]
lineStr := loc[1]
targetLine, err := strconv.Atoi(lineStr)
if err != nil {
fmt.Println(err.Error())
usage()
}
f, err := os.Open(filePath)
if err != nil {
fmt.Println(err.Error())
usage()
}
defer f.Close()
lineScanner := bufio.NewScanner(f)
line := 0
var lastFunc string
for lineScanner.Scan() {
m := funcName.FindStringSubmatch(lineScanner.Text())
if len(m) > 0 {
lastFunc = m[1]
}
if line == targetLine {
fmt.Println(lastFunc)
return
}
line++
}
}
func usage() {
fmt.Fprintf(os.Stderr, "Usage: %s {file:line}\n", os.Args[0])
os.Exit(1)
}
// Look for a func followed by anything and ending at a `(` or ` `(space).
var funcName = regexp.MustCompile(`func ([^ (]+)`)
优点:
- 快速(相对于构建 AST)
- 易于理解(无需了解 AST 和 Depth-First-Search 的工作原理)
- 在 go 代码包含解析器失败的错误时工作(对于某些类型的 linters 或工具,这可能是您想要的)。
缺点:
- 关于代码的假设,我们假设
(
和
是唯一结束 func 声明的字符,并且我们假设代码被格式化以便永远不会有 2 个 func declerations 1 行。
解析、AST、深度优先搜索
更“规范正确”的方法是使用 go 编译器也使用的“官方”go parser 解析 go 代码。这会产生一个 AST(抽象语法树),我们可以使用 DFS(深度优先搜索)算法遍历它以找到包含我们位置的最具体的 AST 节点,同时找到最新的函数 decleration。
package main
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
"os"
"regexp"
"strconv"
"strings"
)
func main() {
if len(os.Args) < 2 {
usage()
}
var pos token.Position
loc := strings.Split(os.Args[1], ":")
if len(loc) >= 2 {
pos.Filename = loc[0]
line, err := strconv.Atoi(loc[1])
if err != nil {
fmt.Println(err.Error())
usage()
}
pos.Line = line
} else {
usage()
}
if len(loc) >= 3 {
col, err := strconv.Atoi(loc[2])
if err != nil {
fmt.Println(err.Error())
usage()
}
pos.Column = col
}
file, err := os.Open(pos.Filename)
if err != nil {
fmt.Println(err.Error())
usage()
}
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "", file, 0)
if err != nil {
fmt.Println(err.Error())
usage()
}
var lastFunc *ast.FuncDecl
ast.Inspect(f, func(n ast.Node) bool {
if n == nil {
return false
}
// Store the most specific function declaration
if funcDecl, ok := n.(*ast.FuncDecl); ok {
lastFunc = funcDecl
}
start := fset.Position(n.Pos())
end := fset.Position(n.End())
// Don't traverse nodes which don't contain the target line
if start.Line > pos.Line || end.Line < pos.Line {
return false
}
// If node starts and stops on the same line
if start.Line == pos.Line && end.Line == pos.Line {
// Don't traverse nodes which don't contain the target column
if start.Column > pos.Column || end.Column < pos.Column {
return false
}
}
// Note, the very last node to be traversed is our target node
return true
})
if lastFunc != nil {
fmt.Println(lastFunc.Name.String())
}
}
func usage() {
fmt.Fprintf(os.Stderr, "Usage: %s {file:line:column}\n", os.Args[0])
os.Exit(1)
}
优点:
- 规范正确,解析器处理所有边缘情况、unicode 字符等。
缺点:
- 因为解析器做了很多工作,构建 AST 树等,所以速度较慢
- 需要更多知识才能理解/使用
- 如果 go 文件包含错误,解析器将出错,因此不会给出结果
仅通过源代码(无运行时检查)从文件位置获取周围函数名称的最佳方法是什么?
例如说我有一些代码:
func MyFunc() {
doSomething() // package/file.go:215:15
}
而且我有doSomething的位置,在package/file.go:215:15
,有没有办法轻松获取MyFunc?
包含“最好”一词的问题总是很难回答,因为这在很大程度上取决于您的要求,而您没有说出具体要求。
我想到了 2 种方法来解决这个问题,都有各自的优缺点列表:
快速而肮脏
快速而肮脏的方法是循环遍历每一行代码,并在我们到达我们感兴趣的行之前查找最近出现的函数减速。这应该是包含我们行的函数。
package main
import (
"bufio"
"fmt"
"os"
"regexp"
"strconv"
"strings"
)
func main() {
if len(os.Args) < 2 {
usage()
}
loc := strings.Split(os.Args[1], ":")
if len(loc) != 2 {
usage()
}
filePath := loc[0]
lineStr := loc[1]
targetLine, err := strconv.Atoi(lineStr)
if err != nil {
fmt.Println(err.Error())
usage()
}
f, err := os.Open(filePath)
if err != nil {
fmt.Println(err.Error())
usage()
}
defer f.Close()
lineScanner := bufio.NewScanner(f)
line := 0
var lastFunc string
for lineScanner.Scan() {
m := funcName.FindStringSubmatch(lineScanner.Text())
if len(m) > 0 {
lastFunc = m[1]
}
if line == targetLine {
fmt.Println(lastFunc)
return
}
line++
}
}
func usage() {
fmt.Fprintf(os.Stderr, "Usage: %s {file:line}\n", os.Args[0])
os.Exit(1)
}
// Look for a func followed by anything and ending at a `(` or ` `(space).
var funcName = regexp.MustCompile(`func ([^ (]+)`)
优点:
- 快速(相对于构建 AST)
- 易于理解(无需了解 AST 和 Depth-First-Search 的工作原理)
- 在 go 代码包含解析器失败的错误时工作(对于某些类型的 linters 或工具,这可能是您想要的)。
缺点:
- 关于代码的假设,我们假设
(
和
解析、AST、深度优先搜索
更“规范正确”的方法是使用 go 编译器也使用的“官方”go parser 解析 go 代码。这会产生一个 AST(抽象语法树),我们可以使用 DFS(深度优先搜索)算法遍历它以找到包含我们位置的最具体的 AST 节点,同时找到最新的函数 decleration。
package main
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
"os"
"regexp"
"strconv"
"strings"
)
func main() {
if len(os.Args) < 2 {
usage()
}
var pos token.Position
loc := strings.Split(os.Args[1], ":")
if len(loc) >= 2 {
pos.Filename = loc[0]
line, err := strconv.Atoi(loc[1])
if err != nil {
fmt.Println(err.Error())
usage()
}
pos.Line = line
} else {
usage()
}
if len(loc) >= 3 {
col, err := strconv.Atoi(loc[2])
if err != nil {
fmt.Println(err.Error())
usage()
}
pos.Column = col
}
file, err := os.Open(pos.Filename)
if err != nil {
fmt.Println(err.Error())
usage()
}
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "", file, 0)
if err != nil {
fmt.Println(err.Error())
usage()
}
var lastFunc *ast.FuncDecl
ast.Inspect(f, func(n ast.Node) bool {
if n == nil {
return false
}
// Store the most specific function declaration
if funcDecl, ok := n.(*ast.FuncDecl); ok {
lastFunc = funcDecl
}
start := fset.Position(n.Pos())
end := fset.Position(n.End())
// Don't traverse nodes which don't contain the target line
if start.Line > pos.Line || end.Line < pos.Line {
return false
}
// If node starts and stops on the same line
if start.Line == pos.Line && end.Line == pos.Line {
// Don't traverse nodes which don't contain the target column
if start.Column > pos.Column || end.Column < pos.Column {
return false
}
}
// Note, the very last node to be traversed is our target node
return true
})
if lastFunc != nil {
fmt.Println(lastFunc.Name.String())
}
}
func usage() {
fmt.Fprintf(os.Stderr, "Usage: %s {file:line:column}\n", os.Args[0])
os.Exit(1)
}
优点:
- 规范正确,解析器处理所有边缘情况、unicode 字符等。
缺点:
- 因为解析器做了很多工作,构建 AST 树等,所以速度较慢
- 需要更多知识才能理解/使用
- 如果 go 文件包含错误,解析器将出错,因此不会给出结果