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/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{}) error
  • Node(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 结构体

字段类型说明默认值
ModeMode打印模式0
Tabwidthint制表符宽度8
Indentint初始缩进级别0
UseSpacesbool使用空格(已废弃)false

Mode 常量

常量说明效果
UseSpaces使用空格代替制表符缩进使用空格
TabIndent使用制表符缩进缩进使用制表符
RawFormat原始格式不格式化
NormalizeNumbers规范化数字数字标准化

包级别函数

函数说明输入输出
Fprint(output, fset, x, cfg)打印 AST(带配置)io.Writer, *FileSet, AST, *Configerror
Fprint(output, fset, x)打印 AST(默认配置)io.Writer, *FileSet, ASTerror
Node(x)转换为字节切片AST[]byte

Config 方法

方法说明
cfg.Fprint(output, fset, x)使用配置打印
cfg.Node(x)使用配置转换为字节切片

使用场景

场景推荐函数配置
打印到文件Fprintnil 或自定义
获取格式化结果Node默认或自定义
使用空格缩进Fprint/NodeMode: UseSpaces
使用制表符缩进Fprint/NodeMode: TabIndent
快速格式化printer.Node默认配置

输出目标

目标推荐方法示例
标准输出FprintFprint(os.Stdout, fset, file, nil)
文件FprintFprint(file, fset, ast, nil)
字符串Nodestring(Node(ast))
网络FprintFprint(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+