go/printer - AST 打印器
go/printer 包提供了将 Go AST(抽象语法树)打印为源码的功能,是 gofmt 工具的核心组件。
概述
go/printer 包用于将 AST 节点转换回格式化的 Go 源代码,支持自定义缩进、制表符宽度等配置选项。
包导入:
import (
"go/printer"
"go/ast"
"go/token"
"fmt"
"os"
)
基本使用:
// 1. 创建 FileSet
fset := token.NewFileSet()
// 2. 解析或创建 AST
file, _ := parser.ParseFile(fset, "main.go", src, 0)
// 3. 打印 AST 到输出流
err := printer.Fprint(os.Stdout, fset, file)
if err != nil {
panic(err)
}
// 4. 使用配置打印
config := &printer.Config{
Mode: printer.UseSpaces,
Tabwidth: 4,
}
config.Fprint(os.Stdout, fset, file)
典型示例:
示例 1:打印 AST 到标准输出:
package main
import (
"go/ast"
"go/parser"
"go/printer"
"go/token"
"os"
)
func main() {
src := `
package main
func main( ) {
x:=1
y:=2
_ = x + y
}
`
// 解析为 AST
fset := token.NewFileSet()
file, _ := parser.ParseFile(fset, "test.go", src, parser.ParseComments)
// 打印到标准输出(自动格式化)
printer.Fprint(os.Stdout, fset, file)
}
运行:
$ go run main.go
package main
func main() {
x := 1
y := 2
_ = x + y
}
示例 2:使用不同配置打印:
package main
import (
"bytes"
"fmt"
"go/ast"
"go/parser"
"go/printer"
"go/token"
)
func main() {
src := `
package main
func Test( ) {
x := 1
}
`
fset := token.NewFileSet()
file, _ := parser.ParseFile(fset, "test.go", src, 0)
// 默认配置
var buf1 bytes.Buffer
printer.Fprint(&buf1, fset, file)
fmt.Printf("默认:\n%s\n", buf1.String())
// 使用空格代替制表符
var buf2 bytes.Buffer
cfg := &printer.Config{
Mode: printer.UseSpaces,
Tabwidth: 4,
}
cfg.Fprint(&buf2, fset, file)
fmt.Printf("使用空格:\n%s\n", buf2.String())
}
运行:
$ go run main.go
默认:
package main
func Test() {
x := 1
}
使用空格:
package main
func Test() {
x := 1
}
一、Config 结构体
打印配置
Config
定义:
type Config struct {
Mode Mode // 打印模式
Tabwidth int // 制表符宽度
Indent int // 初始缩进级别
UseSpaces bool // 使用空格代替制表符(已废弃,使用 Mode)
}
说明:
- 控制 AST 打印的行为
- 可配置缩进、制表符宽度、打印模式等
字段:
Mode:打印模式(位掩码)Tabwidth:制表符宽度(用于对齐)Indent:初始缩进级别UseSpaces:使用空格代替制表符(已废弃)
方法:
Fprint(output io.Writer, fset *token.FileSet, node interface{}) errorNode(node interface{}) []byte
示例:
package main
import (
"bytes"
"fmt"
"go/ast"
"go/parser"
"go/printer"
"go/token"
)
func main() {
src := `
package main
func Test( ) {
x := 1
}
`
fset := token.NewFileSet()
file, _ := parser.ParseFile(fset, "test.go", src, 0)
// 创建配置
config := &printer.Config{
Mode: printer.UseSpaces,
Tabwidth: 4,
Indent: 0,
}
// 打印
var buf bytes.Buffer
config.Fprint(&buf, fset, file)
fmt.Printf("配置打印:\n%s\n", buf.String())
}
运行:
$ go run main.go
配置打印:
package main
func Test() {
x := 1
}
二、Mode 类型
打印模式类型
Mode
定义:
type Mode uint
说明:
- 控制打印器的行为
- 使用位掩码组合多个选项
包级别常量
UseSpaces
定义:
const UseSpaces Mode = 1 << iota
说明:
- 使用空格代替制表符
- 配合
Tabwidth指定空格数量
示例:
package main
import (
"bytes"
"fmt"
"go/parser"
"go/printer"
"go/token"
)
func main() {
src := `
package main
func Test( ) {
x := 1
}
`
fset := token.NewFileSet()
file, _ := parser.ParseFile(fset, "test.go", src, 0)
// 使用空格
var buf bytes.Buffer
cfg := &printer.Config{
Mode: printer.UseSpaces,
Tabwidth: 4,
}
cfg.Fprint(&buf, fset, file)
fmt.Printf("使用空格:\n%q\n", buf.String())
}
运行:
$ go run main.go
使用空格:"package main\n\nfunc Test() {\n x := 1\n}\n"
TabIndent
定义:
const TabIndent Mode = 1 << iota
说明:
- 使用制表符进行缩进
- 默认模式
示例:
package main
import (
"bytes"
"fmt"
"go/parser"
"go/printer"
"go/token"
)
func main() {
src := `
package main
func Test( ) {
x := 1
}
`
fset := token.NewFileSet()
file, _ := parser.ParseFile(fset, "test.go", src, 0)
// 使用制表符缩进
var buf bytes.Buffer
cfg := &printer.Config{
Mode: printer.TabIndent,
Tabwidth: 8,
}
cfg.Fprint(&buf, fset, file)
fmt.Printf("制表符缩进:\n%q\n", buf.String())
}
运行:
$ go run main.go
制表符缩进:"package main\n\nfunc Test() {\n\tx := 1\n}\n"
RawFormat
定义:
const RawFormat Mode = 1 << iota
说明:
- 原始格式输出
- 不进行任何格式化
- 用于调试
NormalizeNumbers
定义:
const NormalizeNumbers Mode = 1 << iota
说明:
- 规范化数字格式
- 将数字转换为标准形式
三、包级别函数(按字母顺序)
Fprint
定义:
func Fprint(output io.Writer, fset *token.FileSet, x interface{}, cfg *Config) error
说明:
- 将 AST 节点打印到输出流
- 可指定配置
参数:
output:输出流(io.Writer)fset:token 文件集x:AST 节点(*ast.File、ast.Expr 等)cfg:打印配置(可为 nil,使用默认配置)
返回值:
error:打印错误
示例:
package main
import (
"fmt"
"go/ast"
"go/parser"
"go/printer"
"go/token"
"os"
)
func main() {
src := `
package main
func Add(a, b int) int {
return a + b
}
`
fset := token.NewFileSet()
file, _ := parser.ParseFile(fset, "add.go", src, 0)
// 使用默认配置打印
err := printer.Fprint(os.Stdout, fset, file, nil)
if err != nil {
panic(err)
}
}
运行:
$ go run main.go
package main
func Add(a, b int) int {
return a + b
}
Fprint(简化版)
定义:
func Fprint(output io.Writer, fset *token.FileSet, x interface{}) error
说明:
- 简化版本的 Fprint
- 使用默认配置
参数:
output:输出流fset:token 文件集x:AST 节点
返回值:
error:打印错误
示例:
package main
import (
"bytes"
"fmt"
"go/parser"
"go/printer"
"go/token"
)
func main() {
src := `package main; func main() {}`
fset := token.NewFileSet()
file, _ := parser.ParseFile(fset, "test.go", src, 0)
var buf bytes.Buffer
printer.Fprint(&buf, fset, file)
fmt.Printf("输出:%s\n", buf.String())
}
运行:
$ go run main.go
输出:package main
func main() {}
Node
定义:
func Node(x interface{}) []byte
说明:
- 将 AST 节点转换为字节切片
- 使用默认配置
参数:
x:AST 节点
返回值:
[]byte:格式化后的源码
示例:
package main
import (
"fmt"
"go/parser"
"go/printer"
)
func main() {
src := `package main; func Test( ) { }`
file, _ := parser.ParseFile(nil, "", src, 0)
// 转换为字节切片
formatted := printer.Node(file)
fmt.Printf("格式化:%s\n", formatted)
}
运行:
$ go run main.go
格式化:package main
func Test() {
}
四、Config 方法
Fprint 方法
定义:
func (cfg *Config) Fprint(output io.Writer, fset *token.FileSet, x interface{}) error
说明:
- Config 结构体的方法版本
- 使用配置的参数打印
参数:
output:输出流fset:token 文件集x:AST 节点
返回值:
error:打印错误
示例:
package main
import (
"bytes"
"fmt"
"go/parser"
"go/printer"
"go/token"
)
func main() {
src := `
package main
func Test( ) {
x := 1
}
`
fset := token.NewFileSet()
file, _ := parser.ParseFile(fset, "test.go", src, 0)
// 自定义配置
cfg := &printer.Config{
Mode: printer.UseSpaces,
Tabwidth: 4,
}
var buf bytes.Buffer
cfg.Fprint(&buf, fset, file)
fmt.Printf("自定义配置:\n%s\n", buf.String())
}
运行:
$ go run main.go
自定义配置:
package main
func Test() {
x := 1
}
Node 方法
定义:
func (cfg *Config) Node(x interface{}) []byte
说明:
- 使用配置将 AST 节点转换为字节切片
- 比 Fprint 更方便获取结果
参数:
x:AST 节点
返回值:
[]byte:格式化后的源码
示例:
package main
import (
"fmt"
"go/parser"
"go/printer"
)
func main() {
src := `package main; func Add( a,b int ) int { return a+b }`
file, _ := parser.ParseFile(nil, "", src, 0)
// 使用配置转换
cfg := &printer.Config{
Mode: printer.UseSpaces,
Tabwidth: 4,
}
formatted := cfg.Node(file)
fmt.Printf("格式化:\n%s\n", formatted)
}
运行:
$ go run main.go
格式化:
package main
func Add(a, b int) int {
return a + b
}
五、快速参考
Config 结构体
| 字段 | 类型 | 说明 | 默认值 |
|---|---|---|---|
| Mode | Mode | 打印模式 | 0 |
| Tabwidth | int | 制表符宽度 | 8 |
| Indent | int | 初始缩进级别 | 0 |
| UseSpaces | bool | 使用空格(已废弃) | false |
Mode 常量
| 常量 | 说明 | 效果 |
|---|---|---|
| UseSpaces | 使用空格代替制表符 | 缩进使用空格 |
| TabIndent | 使用制表符缩进 | 缩进使用制表符 |
| RawFormat | 原始格式 | 不格式化 |
| NormalizeNumbers | 规范化数字 | 数字标准化 |
包级别函数
| 函数 | 说明 | 输入 | 输出 |
|---|---|---|---|
| Fprint(output, fset, x, cfg) | 打印 AST(带配置) | io.Writer, *FileSet, AST, *Config | error |
| Fprint(output, fset, x) | 打印 AST(默认配置) | io.Writer, *FileSet, AST | error |
| Node(x) | 转换为字节切片 | AST | []byte |
Config 方法
| 方法 | 说明 |
|---|---|
| cfg.Fprint(output, fset, x) | 使用配置打印 |
| cfg.Node(x) | 使用配置转换为字节切片 |
使用场景
| 场景 | 推荐函数 | 配置 |
|---|---|---|
| 打印到文件 | Fprint | nil 或自定义 |
| 获取格式化结果 | Node | 默认或自定义 |
| 使用空格缩进 | Fprint/Node | Mode: UseSpaces |
| 使用制表符缩进 | Fprint/Node | Mode: TabIndent |
| 快速格式化 | printer.Node | 默认配置 |
输出目标
| 目标 | 推荐方法 | 示例 |
|---|---|---|
| 标准输出 | Fprint | Fprint(os.Stdout, fset, file, nil) |
| 文件 | Fprint | Fprint(file, fset, ast, nil) |
| 字符串 | Node | string(Node(ast)) |
| 网络 | Fprint | Fprint(conn, fset, ast, nil) |
六、最佳实践
1. 格式化 Go 代码
package main
import (
"go/parser"
"go/printer"
"go/token"
"os"
)
func formatCode(src []byte) ([]byte, error) {
fset := token.NewFileSet()
file, err := parser.ParseFile(fset, "", src, parser.ParseComments)
if err != nil {
return nil, err
}
var buf []byte
buf, err = printer.Node(file), nil
return buf, err
}
2. 写入文件
package main
import (
"go/parser"
"go/printer"
"go/token"
"os"
)
func writeFormattedFile(input, output string) error {
src, err := os.ReadFile(input)
if err != nil {
return err
}
fset := token.NewFileSet()
file, err := parser.ParseFile(fset, input, src, parser.ParseComments)
if err != nil {
return err
}
out, err := os.Create(output)
if err != nil {
return err
}
defer out.Close()
return printer.Fprint(out, fset, file, nil)
}
3. 自定义缩进
package main
import (
"bytes"
"go/parser"
"go/printer"
)
func formatWithIndent(src []byte, indent int) []byte {
fset := token.NewFileSet()
file, _ := parser.ParseFile(fset, "", src, 0)
cfg := &printer.Config{
Mode: printer.UseSpaces,
Tabwidth: indent,
}
return cfg.Node(file)
}
4. 批量格式化
package main
import (
"bytes"
"go/ast"
"go/parser"
"go/printer"
"go/token"
)
func formatMultipleFiles(files map[string]string) map[string][]byte {
fset := token.NewFileSet()
results := make(map[string][]byte)
for name, src := range files {
file, _ := parser.ParseFile(fset, name, src, 0)
var buf bytes.Buffer
printer.Fprint(&buf, fset, file, nil)
results[name] = buf.Bytes()
}
return results
}
5. 保留注释格式化
package main
import (
"go/parser"
"go/printer"
"go/token"
"os"
)
func formatWithComments(filename string) error {
fset := token.NewFileSet()
// 解析时保留注释
file, err := parser.ParseFile(fset, filename, nil, parser.ParseComments)
if err != nil {
return err
}
// 打印(自动保留注释)
return printer.Fprint(os.Stdout, fset, file, nil)
}
七、注意事项
1. Config 配置影响输出
// 默认配置(制表符)
printer.Fprint(output, fset, file, nil)
// 使用空格
cfg := &printer.Config{Mode: printer.UseSpaces, Tabwidth: 4}
printer.Fprint(output, fset, file, cfg)
2. Node vs Fprint
// Node:直接获取结果
formatted := printer.Node(ast)
// Fprint:写入输出流
printer.Fprint(writer, fset, ast, nil)
3. 性能考虑
- Node 会分配新内存
- Fprint 直接写入,更高效
- 大批量处理使用 Fprint
最后更新:2026-04-04
Go 版本:Go 1.23+