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/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.Writererror
NodeBytes(fset, node)格式化 AST 到字节切片*token.FileSet, AST[]byte
NodeString(fset, node)格式化 AST 到字符串*token.FileSet, ASTstring

使用场景

场景推荐函数优点
格式化源码字符串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+