Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

go/parser - Go 源码解析器

go/parser 包提供了 Go 源代码的解析功能,将源码字符串解析为抽象语法树(AST)。

概述

go/parser 包用于解析 Go 源代码并生成 AST,是 Go 代码分析工具的核心组件,与 go/astgo/token 包配合使用。

包导入

import (
    "go/parser"
    "go/ast"
    "go/token"
    "fmt"
)

基本使用

// 1. 创建 FileSet
fset := token.NewFileSet()

// 2. 解析源码
src := `package main; func main() {}`
file, err := parser.ParseFile(fset, "main.go", src, 0)
if err != nil {
    panic(err)
}

// 3. 遍历 AST
ast.Inspect(file, func(n ast.Node) bool {
    fmt.Printf("%T\n", n)
    return true
})

典型示例

示例 1:解析字符串源码

package main

import (
    "fmt"
    "go/parser"
    "go/token"
)

func main() {
    src := `
package main

import "fmt"

func main() {
    fmt.Println("Hello, World!")
}
`

    // 创建 FileSet
    fset := token.NewFileSet()

    // 解析源码
    file, err := parser.ParseFile(fset, "hello.go", src, parser.ParseComments)
    if err != nil {
        panic(err)
    }

    fmt.Printf("包名:%s\n", file.Name.Name)
    fmt.Printf("文件:%s\n", fset.Position(file.Pos()).Filename)
    fmt.Printf("声明数量:%d\n", len(file.Decls))
}

运行

$ go run main.go
包名:main
文件:hello.go
声明数量:2

示例 2:解析文件并提取信息

package main

import (
    "fmt"
    "go/parser"
    "go/token"
)

func main() {
    // 解析当前目录的文件
    fset := token.NewFileSet()
    pkgs, err := parser.ParseDir(fset, ".", nil, parser.ParseComments)
    if err != nil {
        panic(err)
    }

    // 遍历所有包
    for pkgName, pkg := range pkgs {
        fmt.Printf("包:%s\n", pkgName)
        fmt.Printf("  文件数:%d\n", len(pkg.Files))
        
        // 遍历包中的文件
        for fileName, file := range pkg.Files {
            fmt.Printf("  文件:%s\n", fileName)
            fmt.Printf("    函数数:%d\n", len(file.Scope.Objects))
        }
    }
}

运行

$ go run main.go
包:main
  文件数:1
  文件:main.go
    函数数:2

一、Mode 类型

解析模式类型

Mode

定义

type Mode uint

说明

  • 控制解析器的行为
  • 使用位掩码组合多个选项

包级别常量

ParseComments

定义

const ParseComments Mode = 1 << iota

说明

  • 解析注释(默认不解析)
  • 注释存储在 AST 的 Doc 字段中

示例

package main

import (
    "fmt"
    "go/parser"
    "go/token"
)

func main() {
    src := `
package main

// Hello 是问候函数
func Hello() {
    // 打印问候语
    println("Hello")
}
`

    fset := token.NewFileSet()
    
    // 不解析注释
    file1, _ := parser.ParseFile(fset, "", src, 0)
    fmt.Printf("无注释:%v\n", file1.Decls[0].(*ast.FuncDecl).Doc != nil)
    
    // 解析注释
    file2, _ := parser.ParseFile(fset, "", src, parser.ParseComments)
    fmt.Printf("有注释:%v\n", file2.Decls[0].(*ast.FuncDecl).Doc != nil)
}

运行

$ go run main.go
无注释:false
有注释:true

DeclarationErrors

定义

const DeclarationErrors Mode = 1 << iota

说明

  • 报告声明错误
  • 如重复声明、未使用等

示例

package main

import (
    "fmt"
    "go/parser"
    "go/token"
)

func main() {
    src := `
package main

var x int
var x int  // 重复声明
`

    fset := token.NewFileSet()
    
    // 不报告声明错误
    _, err1 := parser.ParseFile(fset, "", src, 0)
    fmt.Printf("无错误报告:%v\n", err1 == nil)
    
    // 报告声明错误
    _, err2 := parser.ParseFile(fset, "", src, parser.DeclarationErrors)
    fmt.Printf("有错误报告:%v\n", err2 != nil)
}

运行

$ go run main.go
无错误报告:true
有错误报告:true

AllErrors

定义

const AllErrors Mode = 1 << iota

说明

  • 报告所有错误(不仅仅是第一个)
  • 用于完整的错误检查

示例

package main

import (
    "fmt"
    "go/parser"
    "go/token"
)

func main() {
    src := `
package main

func test( {
    x := 1
    y := 2
    return x + y
}
`

    fset := token.NewFileSet()
    
    // 只报告第一个错误
    _, err1 := parser.ParseFile(fset, "", src, 0)
    fmt.Printf("单个错误:%v\n", err1)
    
    // 报告所有错误
    _, err2 := parser.ParseFile(fset, "", src, parser.AllErrors)
    fmt.Printf("所有错误:%v\n", err2)
}

Trace

定义

const Trace Mode = 1 << iota

说明

  • 启用解析跟踪
  • 用于调试解析过程
  • 输出到标准错误

SkipObjectResolution

定义

const SkipObjectResolution Mode = 1 << iota

说明

  • 跳过对象解析
  • 更快但不提供符号信息

二、包级别函数(按字母顺序)

解析表达式

ParseExpr

定义

func ParseExpr(x string) (ast.Expr, error)

说明

  • 解析单个表达式
  • 返回表达式 AST 节点

参数

  • x:表达式字符串

返回值

  • ast.Expr:表达式 AST 节点
  • error:解析错误

示例

package main

import (
    "fmt"
    "go/parser"
)

func main() {
    // 解析简单表达式
    expr1, _ := parser.ParseExpr("a + b")
    fmt.Printf("类型:%T\n", expr1)
    
    // 解析函数调用
    expr2, _ := parser.ParseExpr("fmt.Println(x)")
    fmt.Printf("类型:%T\n", expr2)
    
    // 解析复杂表达式
    expr3, _ := parser.ParseExpr("x.y[z]")
    fmt.Printf("类型:%T\n", expr3)
}

运行

$ go run main.go
类型:*ast.BinaryExpr
类型:*ast.CallExpr
类型:*ast.IndexExpr

解析文件

ParseFile

定义

func ParseFile(fset *token.FileSet, filename string, src interface{}, mode Mode) (*ast.File, error)

说明

  • 解析单个 Go 源文件
  • 最常用的解析函数

参数

  • fset:token 文件集
  • filename:文件名(用于错误信息)
  • src:源码(可以是 string、[]byte、io.Reader 或 nil)
  • mode:解析模式

返回值

  • *ast.File:文件 AST
  • error:解析错误

示例

package main

import (
    "fmt"
    "go/parser"
    "go/token"
    "os"
)

func main() {
    fset := token.NewFileSet()
    
    // 从字符串解析
    src := `package main; func main() {}`
    file1, _ := parser.ParseFile(fset, "test.go", src, 0)
    fmt.Printf("字符串:%s\n", file1.Name.Name)
    
    // 从字节切片解析
    srcBytes := []byte(`package main; var x int`)
    file2, _ := parser.ParseFile(fset, "test.go", srcBytes, 0)
    fmt.Printf("字节:%s\n", file2.Name.Name)
    
    // 从文件解析
    file3, _ := parser.ParseFile(fset, "main.go", nil, 0)
    if file3 != nil {
        fmt.Printf("文件:%s\n", file3.Name.Name)
    }
}

解析目录

ParseDir

定义

func ParseDir(fset *token.FileSet, path string, filter func(os.FileInfo) bool, mode Mode) (map[string]*ast.Package, error)

说明

  • 解析目录中的所有 Go 文件
  • 返回包名到包 AST 的映射

参数

  • fset:token 文件集
  • path:目录路径
  • filter:文件过滤器(可为 nil)
  • mode:解析模式

返回值

  • map[string]*ast.Package:包名到包 AST 的映射
  • error:解析错误

示例

package main

import (
    "fmt"
    "go/parser"
    "go/token"
)

func main() {
    fset := token.NewFileSet()
    
    // 解析当前目录
    pkgs, err := parser.ParseDir(fset, ".", nil, parser.ParseComments)
    if err != nil {
        panic(err)
    }
    
    // 遍历所有包
    for name, pkg := range pkgs {
        fmt.Printf("包:%s\n", name)
        fmt.Printf("  文件数:%d\n", len(pkg.Files))
        
        // 遍历文件
        for fname := range pkg.Files {
            fmt.Printf("    - %s\n", fname)
        }
    }
}

运行

$ go run main.go
包:main
  文件数:1
    - main.go

带过滤器的目录解析

ParseDirWithFilter

定义

func ParseDirWithFilter(fset *token.FileSet, path string, filter func(os.FileInfo) bool, mode Mode) (map[string]*ast.Package, error)

说明

  • 与 ParseDir 相同,但支持文件过滤

示例

package main

import (
    "fmt"
    "go/parser"
    "go/token"
    "os"
    "strings"
)

func main() {
    fset := token.NewFileSet()
    
    // 只解析 _test.go 文件
    filter := func(info os.FileInfo) bool {
        return strings.HasSuffix(info.Name(), "_test.go")
    }
    
    pkgs, _ := parser.ParseDir(fset, ".", filter, 0)
    
    for name, pkg := range pkgs {
        fmt.Printf("测试包:%s\n", name)
        fmt.Printf("  测试文件数:%d\n", len(pkg.Files))
    }
}

三、快速参考

Mode 常量

常量说明使用场景
ParseComments解析注释需要提取文档
DeclarationErrors报告声明错误完整错误检查
AllErrors报告所有错误完整错误列表
Trace启用解析跟踪调试解析过程
SkipObjectResolution跳过对象解析快速解析

包级别函数

函数说明输入输出
ParseExpr(x)解析表达式string(ast.Expr, error)
ParseFile(fset, filename, src, mode)解析文件*FileSet, string, interface{}, Mode(*ast.File, error)
ParseDir(fset, path, filter, mode)解析目录*FileSet, string, Filter, Mode(map[string]*Package, error)

src 参数类型

类型说明示例
string源码字符串parser.ParseFile(fset, "", "package main", 0)
[]byte源码字节切片parser.ParseFile(fset, "", []byte(code), 0)
io.Reader源码读取器parser.ParseFile(fset, "", reader, 0)
nil从文件读取parser.ParseFile(fset, "main.go", nil, 0)

使用场景

场景推荐函数模式
解析单个表达式ParseExpr0
解析源码字符串ParseFileParseComments
解析文件ParseFile0
解析整个目录ParseDirParseComments
只解析测试文件ParseDir0 + 过滤器
快速解析ParseFileSkipObjectResolution
完整错误检查ParseFileAllErrors

常见错误

错误信息原因解决方法
expected ‘;’缺少分号Go 会自动添加,检查语法
expected ‘}’缺少右括号检查括号匹配
expected operand缺少操作数检查表达式完整性
expected type缺少类型检查类型声明

四、最佳实践

1. 解析并遍历 AST

package main

import (
    "fmt"
    "go/ast"
    "go/parser"
    "go/token"
)

func parseAndWalk(filename string, src string) error {
    fset := token.NewFileSet()
    file, err := parser.ParseFile(fset, filename, src, parser.ParseComments)
    if err != nil {
        return err
    }
    
    ast.Inspect(file, func(n ast.Node) bool {
        if fn, ok := n.(*ast.FuncDecl); ok {
            fmt.Printf("函数:%s\n", fn.Name.Name)
        }
        return true
    })
    
    return nil
}

2. 提取包文档

package main

import (
    "fmt"
    "go/parser"
    "go/token"
)

func extractPackageDoc(filename string, src string) {
    fset := token.NewFileSet()
    file, _ := parser.ParseFile(fset, filename, src, parser.ParseComments)
    
    if file.Doc != nil {
        fmt.Printf("包文档:%s\n", file.Doc.Text())
    }
}

3. 错误处理和报告

package main

import (
    "fmt"
    "go/parser"
    "go/token"
)

func safeParse(src string) {
    fset := token.NewFileSet()
    _, err := parser.ParseFile(fset, "test.go", src, parser.AllErrors)
    
    if err != nil {
        // 打印详细错误信息
        fmt.Printf("解析错误:%v\n", err)
        
        // 获取错误位置
        if pos, ok := err.(interface{ Pos() token.Pos }); ok {
            fmt.Printf("位置:%s\n", fset.Position(pos.Pos()))
        }
    }
}

4. 解析多个文件

package main

import (
    "fmt"
    "go/parser"
    "go/token"
)

func parseMultipleFiles(files map[string]string) {
    fset := token.NewFileSet()
    
    for filename, src := range files {
        file, err := parser.ParseFile(fset, filename, src, parser.ParseComments)
        if err != nil {
            fmt.Printf("解析 %s 失败:%v\n", filename, err)
            continue
        }
        
        fmt.Printf("解析 %s 成功:%d 个声明\n", 
            filename, len(file.Decls))
    }
}

5. 自定义文件过滤器

package main

import (
    "fmt"
    "go/parser"
    "go/token"
    "os"
    "strings"
)

func parseWithFilter(dir string) {
    fset := token.NewFileSet()
    
    // 只解析非测试文件
    filter := func(info os.FileInfo) bool {
        return !strings.HasSuffix(info.Name(), "_test.go")
    }
    
    pkgs, err := parser.ParseDir(fset, dir, filter, parser.ParseComments)
    if err != nil {
        panic(err)
    }
    
    for name, pkg := range pkgs {
        fmt.Printf("包 %s: %d 个文件\n", name, len(pkg.Files))
    }
}

五、注意事项

1. FileSet 必须共享

// 错误:创建多个 FileSet
fset1 := token.NewFileSet()
file1, _ := parser.ParseFile(fset1, "a.go", src1, 0)

fset2 := token.NewFileSet()
file2, _ := parser.ParseFile(fset2, "b.go", src2, 0)
// 位置信息无法比较

// 正确:共享 FileSet
fset := token.NewFileSet()
file1, _ := parser.ParseFile(fset, "a.go", src1, 0)
file2, _ := parser.ParseFile(fset, "b.go", src2, 0)

2. 解析模式的选择

// 只需要 AST 结构
file, _ := parser.ParseFile(fset, "", src, 0)

// 需要文档注释
file, _ = parser.ParseFile(fset, "", src, parser.ParseComments)

// 需要完整错误检查
file, _ = parser.ParseFile(fset, "", src, parser.AllErrors)

3. 内存考虑

  • 解析大型项目会消耗大量内存
  • 使用 SkipObjectResolution 可以减少内存使用
  • 考虑分批次解析文件

最后更新:2026-04-04
Go 版本:Go 1.23+