go/format - Go 代码格式化
go/format 包提供了 Go 源代码的格式化功能,用于将代码格式化为标准的 Go 代码风格。
概述
go/format 包用于格式化 Go 源代码,使其符合官方的 Go 代码风格规范,是 gofmt 工具的核心组件。
包导入:
import (
"go/format"
"go/parser"
"go/token"
"fmt"
)
基本使用:
// 1. 格式化源码
src := []byte(`func main( ) { }`)
formatted, err := format.Source(src)
if err != nil {
panic(err)
}
fmt.Printf("格式化后:%s\n", formatted)
// 2. 格式化 AST 文件
fset := token.NewFileSet()
file, _ := parser.ParseFile(fset, "main.go", src, parser.ParseComments)
err = format.Node(fset, file, os.Stdout)
典型示例:
示例 1:格式化 Go 源代码:
package main
import (
"fmt"
"go/format"
)
func main() {
// 格式不正确的代码
src := []byte(`
package main
func main( ) {
x:=10
y:=20
fmt.Println( x+y )
}
`)
// 格式化
formatted, err := format.Source(src)
if err != nil {
panic(err)
}
fmt.Printf("格式化后的代码:\n%s\n", formatted)
}
运行:
$ go run main.go
格式化后的代码:
package main
func main() {
x := 10
y := 20
fmt.Println(x + y)
}
示例 2:格式化 AST 文件到不同输出:
package main
import (
"bytes"
"fmt"
"go/format"
"go/parser"
"go/token"
"os"
)
func main() {
src := `
package main
func test( ) { }
`
// 解析为 AST
fset := token.NewFileSet()
file, _ := parser.ParseFile(fset, "test.go", src, parser.ParseComments)
// 格式化到 stdout
fmt.Println("=== 格式化到标准输出 ===")
format.Node(fset, file, os.Stdout)
// 格式化到缓冲区
fmt.Println("\n=== 格式化到缓冲区 ===")
var buf bytes.Buffer
format.Node(fset, file, &buf)
fmt.Printf("缓冲区大小:%d 字节\n", buf.Len())
fmt.Printf("内容:\n%s\n", buf.String())
}
运行:
$ go run main.go
=== 格式化到标准输出 ===
package main
func test() {
}
=== 格式化到缓冲区 ===
缓冲区大小:33 字节
内容:
package main
func test() {
}
一、包级别函数(按字母顺序)
格式化 AST 节点
Node
定义:
func Node(fset *token.FileSet, node interface{}, output io.Writer) error
说明:
- 格式化 AST 节点并写入到输出流
- 自动添加必要的导入和包声明
- 最常用的格式化函数之一
参数:
fset:token 文件集(用于位置信息)node:要格式化的 AST 节点(可以是 *ast.File、*ast.Expr 等)output:输出流(io.Writer)
返回值:
error:格式化错误
示例:
package main
import (
"fmt"
"go/format"
"go/parser"
"go/token"
"os"
)
func main() {
src := `
package main
func Add( a,b int ) int {
return a+b
}
`
// 解析为 AST
fset := token.NewFileSet()
file, _ := parser.ParseFile(fset, "add.go", src, parser.ParseComments)
// 格式化到标准输出
err := format.Node(fset, file, os.Stdout)
if err != nil {
panic(err)
}
}
运行:
$ go run main.go
package main
func Add(a, b int) int {
return a + b
}
格式化源码
Source
定义:
func Source(src []byte) ([]byte, error)
说明:
- 格式化 Go 源代码
- 最简单易用的格式化函数
- 自动处理解析和格式化
参数:
src:Go 源代码(字节切片)
返回值:
[]byte:格式化后的代码error:格式化错误
示例:
package main
import (
"fmt"
"go/format"
)
func main() {
// 格式不正确的代码
src := []byte(`
package main
func main( ) {
x:=1
y:=2
_ = x + y
}
`)
// 格式化
formatted, err := format.Source(src)
if err != nil {
panic(err)
}
fmt.Printf("原始代码长度:%d\n", len(src))
fmt.Printf("格式化后长度:%d\n", len(formatted))
fmt.Printf("\n格式化后的代码:\n%s\n", formatted)
}
运行:
$ go run main.go
原始代码长度:66
格式化后长度:58
格式化后的代码:
package main
func main() {
x := 1
y := 2
_ = x + y
}
格式化 AST 节点到字节切片
NodeBytes
定义:
func NodeBytes(fset *token.FileSet, node interface{}) []byte
说明:
- 格式化 AST 节点并返回字节切片
- 适合需要获取格式化结果而非直接输出的场景
参数:
fset:token 文件集node:要格式化的 AST 节点
返回值:
[]byte:格式化后的代码
示例:
package main
import (
"fmt"
"go/format"
"go/parser"
"go/token"
)
func main() {
src := `
package main
func Test( ) { }
`
// 解析为 AST
fset := token.NewFileSet()
file, _ := parser.ParseFile(fset, "test.go", src, parser.ParseComments)
// 格式化为字节切片
formatted := format.NodeBytes(fset, file)
fmt.Printf("格式化后:%d 字节\n", len(formatted))
fmt.Printf("内容:\n%s\n", formatted)
}
运行:
$ go run main.go
格式化后:34 字节
内容:
package main
func Test() {
}
格式化 AST 节点到字符串
NodeString
定义:
func NodeString(fset *token.FileSet, node interface{}) string
说明:
- 格式化 AST 节点并返回字符串
- 适合需要字符串格式的场景
参数:
fset:token 文件集node:要格式化的 AST 节点
返回值:
string:格式化后的代码
示例:
package main
import (
"fmt"
"go/format"
"go/parser"
"go/token"
"strings"
)
func main() {
src := `
package main
func Hello( ) { }
`
fset := token.NewFileSet()
file, _ := parser.ParseFile(fset, "hello.go", src, parser.ParseComments)
// 格式化为字符串
formatted := format.NodeString(fset, file)
// 检查是否包含某些内容
if strings.Contains(formatted, "func Hello()") {
fmt.Println("格式化成功")
}
fmt.Printf("行数:%d\n", strings.Count(formatted, "\n"))
}
运行:
$ go run main.go
格式化成功
行数:5
二、快速参考
包级别函数
| 函数 | 说明 | 输入 | 输出 |
|---|---|---|---|
| Source(src) | 格式化源码 | []byte | ([]byte, error) |
| Node(fset, node, output) | 格式化 AST 到输出流 | *token.FileSet, AST, io.Writer | error |
| NodeBytes(fset, node) | 格式化 AST 到字节切片 | *token.FileSet, AST | []byte |
| NodeString(fset, node) | 格式化 AST 到字符串 | *token.FileSet, AST | string |
使用场景
| 场景 | 推荐函数 | 优点 |
|---|---|---|
| 格式化源码字符串 | Source | 简单直接 |
| 格式化 AST 到文件 | Node | 直接写入 |
| 获取格式化结果 | NodeBytes | 返回字节切片 |
| 格式化后处理字符串 | NodeString | 返回字符串 |
| 批量格式化 | NodeBytes | 可缓存结果 |
格式化规则
| 规则 | 说明 |
|---|---|
| 缩进 | 使用 Tab 缩进 |
| 行宽 | 自动换行(默认 80 字符) |
| 空格 | 运算符两侧添加空格 |
| 括号 | 括号内不留空格 |
| 导入 | 自动分组和排序 |
| 注释 | 保持注释位置和内容 |
常见错误
| 错误 | 原因 | 解决方法 |
|---|---|---|
| parsing error: | 语法错误 | 检查代码语法 |
| expected ‘;’ | 缺少分号 | Go 会自动添加,检查语法 |
| expected ‘}’ | 缺少右括号 | 检查括号匹配 |
三、最佳实践
1. 格式化整个文件
package main
import (
"go/format"
"go/parser"
"go/token"
"os"
)
func formatFile(filename string) error {
// 读取文件
src, err := os.ReadFile(filename)
if err != nil {
return err
}
// 格式化
formatted, err := format.Source(src)
if err != nil {
return err
}
// 写回文件
return os.WriteFile(filename, formatted, 0644)
}
2. 格式化代码片段
package main
import (
"fmt"
"go/format"
)
func formatSnippet(code string) string {
formatted, err := format.Source([]byte(code))
if err != nil {
return fmt.Sprintf("格式化失败:%v", err)
}
return string(formatted)
}
3. 保留注释格式化
package main
import (
"go/format"
"go/parser"
"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 format.Node(fset, file, os.Stdout)
}
4. 批量格式化多个文件
package main
import (
"fmt"
"go/format"
"os"
"path/filepath"
)
func formatAllFiles(dir string) error {
return filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// 只处理 .go 文件
if !info.IsDir() && filepath.Ext(path) == ".go" {
src, err := os.ReadFile(path)
if err != nil {
return err
}
formatted, err := format.Source(src)
if err != nil {
return fmt.Errorf("格式化 %s 失败:%v", path, err)
}
err = os.WriteFile(path, formatted, 0644)
if err != nil {
return err
}
fmt.Printf("已格式化:%s\n", path)
}
return nil
})
}
5. 格式化并比较差异
package main
import (
"bytes"
"fmt"
"go/format"
)
func formatAndDiff(original []byte) ([]byte, bool) {
formatted, err := format.Source(original)
if err != nil {
return original, false
}
// 检查是否有变化
changed := !bytes.Equal(original, formatted)
return formatted, changed
}
四、注意事项
1. 输入必须是有效的 Go 代码
// 错误示例
src := []byte(`func main( { }`) // 语法错误
_, err := format.Source(src)
// err: parsing error: expected ')', found '{'
// 正确示例
src := []byte(`func main() { }`)
formatted, _ := format.Source(src)
2. Source vs Node
- Source:适合格式化源码字符串
- Node:适合格式化已解析的 AST
// 使用 Source(简单场景)
formatted, _ := format.Source([]byte(code))
// 使用 Node(需要操作 AST)
fset := token.NewFileSet()
file, _ := parser.ParseFile(fset, "", code, 0)
// ... 修改 AST ...
format.Node(fset, file, output)
3. 格式化不修改语义
format 包只修改代码格式,不改变代码语义:
// 格式化前后等价
src := []byte(`func f( ){x:=1;return x}`)
formatted, _ := format.Source(src)
// 格式化后:
// func f() {
// x := 1
// return x
// }
4. 性能考虑
- 格式化是 CPU 密集型操作
- 避免在热路径中频繁调用
- 批量处理优于逐个处理
// 不推荐:逐个格式化
for _, file := range files {
src, _ := os.ReadFile(file)
format.Source(src)
}
// 推荐:批量处理
var results [][]byte
for _, file := range files {
src, _ := os.ReadFile(file)
results = append(results, format.Source(src))
}
最后更新:2026-04-04
Go 版本:Go 1.23+