A Go code reordering tool that organizes declarations according to configurable conventions.
# Install
go install github.com/toejough/go-reorder/cmd/go-reorder@latest
# Check which files need reordering
go-reorder -c ./...
# Fix all files in place
go-reorder -w ./...import "github.com/toejough/go-reorder"
// Reorder source code with default config
result, err := reorder.Source(sourceCode)
// Or with custom config
cfg, _ := reorder.LoadConfig(".go-reorder.toml")
result, err := reorder.SourceWithConfig(sourceCode, cfg)Before - declarations scattered throughout the file:
package user
func validateEmail(email string) bool { return email != "" }
type User struct { ID int; Name string }
const MaxUsers = 100
func NewUser(name string) *User { return &User{Name: name} }
var defaultUser = &User{Name: "guest"}
func (u *User) String() string { return u.Name }After - organized by convention:
package user
// Exported constants.
const (
MaxUsers = 100
)
// Exported variables.
var (
defaultUser = &User{Name: "guest"}
)
type User struct { ID int; Name string }
func NewUser(name string) *User { return &User{Name: name} }
func (u *User) String() string { return u.Name }
func validateEmail(email string) bool { return email != "" }- Configurable ordering via TOML config files
- CLI tool for processing files and directories
- Library API for programmatic use
- Preserves all comments and documentation
- Groups types with their constructors and methods
- Handles enum types (iota blocks paired with their type definitions)
- Merges scattered const/var declarations into organized blocks
- Safety modes to prevent accidental code loss
go install github.com/toejough/go-reorder/cmd/go-reorder@latestgo get github.com/toejough/go-reorder# Process a single file (output to stdout)
go-reorder main.go
# Process and write back to file
go-reorder -w main.go
# Process all Go files in a directory recursively
go-reorder -w ./...
# Check if files need reordering (exit 1 if changes needed)
go-reorder -c ./...
# Show diff of what would change
go-reorder -d main.go
# Read from stdin, write to stdout
cat main.go | go-reorder -
# Use explicit config file
go-reorder --config=.go-reorder.toml -w .
# Verbose output (shows config file, mode, file count)
go-reorder -v -w ./...| Flag | Short | Description |
|---|---|---|
--write |
-w |
Write result to source file instead of stdout |
--check |
-c |
Check if files are properly ordered (exit 1 if not) |
--diff |
-d |
Display diff instead of reordered source |
--verbose |
-v |
Show config and processing details |
--config |
Path to config file | |
--mode |
Behavior mode: strict, warn, append, or drop |
|
--exclude |
Exclude files matching pattern (can be repeated) | |
--init |
Create a default .go-reorder.toml config file |
|
--list-sections |
List available section names for config |
When --check finds files that need reordering, it shows details:
config: .go-reorder.toml
pkg/server/handler.go
found: Imports -> unexported functions -> Exported Types
expected: Imports -> Exported Types -> unexported functions
pkg/server/utils.go
sections: Imports -> Exported Functions
issue: within-section reordering needed (e.g., alphabetizing, type grouping)
Add to your .pre-commit-config.yaml:
repos:
- repo: https://github.com/toejough/go-reorder
rev: main # or specific version
hooks:
- id: go-reorder # Auto-fix files
# OR
- id: go-reorder-check # Check only (CI-friendly)# .github/workflows/lint.yml
name: Lint
on: [push, pull_request]
jobs:
reorder-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.23'
- run: go install github.com/toejough/go-reorder/cmd/go-reorder@latest
- run: go-reorder -c ./....PHONY: reorder reorder-check
reorder:
go-reorder -w ./...
reorder-check:
go-reorder -c ./...// Reorder fixes declaration ordering in all Go files.
func Reorder() error {
return sh.Run("go-reorder", "-w", "./...")
}
// ReorderCheck verifies declaration ordering without modifying files.
func ReorderCheck() error {
return sh.Run("go-reorder", "-c", "./...")
}Add to .vscode/tasks.json:
{
"version": "2.0.0",
"tasks": [
{
"label": "go-reorder: fix",
"type": "shell",
"command": "go-reorder",
"args": ["-w", "${file}"],
"problemMatcher": [],
"presentation": { "reveal": "silent" }
},
{
"label": "go-reorder: check all",
"type": "shell",
"command": "go-reorder",
"args": ["-c", "./..."],
"problemMatcher": []
}
]
}Run with Ctrl+Shift+P → "Tasks: Run Task" → select task.
The CLI automatically discovers config files by walking up from the processed file's directory. It stops when it finds:
.go-reorder.toml- uses this config.gitdirectory - stops searching, uses defaultsgo.modfile - stops searching, uses defaults
This means you can have different configs for different modules in a monorepo.
[sections]
order = [
"imports",
"main",
"init",
"exported_consts",
"exported_enums",
"exported_vars",
"exported_types",
"exported_funcs",
"unexported_consts",
"unexported_enums",
"unexported_vars",
"unexported_types",
"unexported_funcs",
"uncategorized",
]
[types]
type_layout = ["typedef", "constructors", "exported_methods", "unexported_methods"]
enum_layout = ["typedef", "iota", "exported_methods", "unexported_methods"]
[behavior]
mode = "strict" # strict | warn | append | drop| Mode | Description |
|---|---|
strict |
Error if code has no matching section in config (default) |
warn |
Append unmatched code at end with warning to stderr |
append |
Silently append unmatched code at end |
drop |
Discard unmatched code (dangerous - use for splitting files) |
| Section | Description |
|---|---|
imports |
Import declarations |
main |
The main() function |
init |
All init() functions (original order preserved) |
exported_consts |
Exported constant declarations |
exported_enums |
Exported enum types with their iota blocks |
exported_vars |
Exported variable declarations |
exported_types |
Exported type definitions with constructors and methods |
exported_funcs |
Exported standalone functions |
unexported_* |
Unexported equivalents of the above |
uncategorized |
Catch-all for anything not matching other sections |
For type_layout:
typedef- The type definition itselfconstructors- Constructor functions (functions namedNewTypeNameorNewXxxTypeName)exported_methods- Exported methods on the typeunexported_methods- Unexported methods on the type
For enum_layout:
typedef- The enum type definition (e.g.,type Status int)iota- The associated iota const blockexported_methods/unexported_methods- Methods on the enum type
Default config works well. Types grouped with constructors and methods.
[sections]
order = [
"imports",
"exported_consts",
"exported_vars",
"exported_types", # Models, services
"unexported_types", # Internal helpers
"unexported_funcs", # Internal helpers
"exported_funcs", # Handlers last - easy to find
"uncategorized",
][sections]
order = [
"imports",
"main", # Entry point at top
"init",
"exported_types", # Command structs
"exported_funcs", # Command implementations
"unexported_funcs",
"uncategorized",
][behavior]
mode = "append" # Don't error on uncategorized codepackage main
import (
"os"
"github.com/toejough/go-reorder"
)
func main() {
content, _ := os.ReadFile("example.go")
// Reorder with default config
reordered, err := reorder.Source(string(content))
if err != nil {
panic(err)
}
os.WriteFile("example.go", []byte(reordered), 0644)
}// Load config from file
cfg, err := reorder.LoadConfig(".go-reorder.toml")
if err != nil {
panic(err)
}
// Or use default and modify
cfg := reorder.DefaultConfig()
cfg.Behavior.Mode = "append"
// Reorder with config
reordered, err := reorder.SourceWithConfig(string(content), cfg)For advanced use cases, work directly with parsed AST files using dave/dst:
import (
"go/token"
"github.com/dave/dst/decorator"
"github.com/toejough/go-reorder"
)
// Parse source to DST
dec := decorator.NewDecorator(token.NewFileSet())
file, err := dec.Parse(sourceCode)
if err != nil {
panic(err)
}
// Reorder in place
err = reorder.File(file) // or FileWithConfig(file, cfg)
if err != nil {
panic(err)
}
// Modify the AST further if needed
// ...
// Print back to source
res := decorator.NewRestorer()
var buf bytes.Buffer
res.Fprint(&buf, file)
result := buf.String()| Function | Description |
|---|---|
Source(src string) |
Reorder source code with default config |
SourceWithConfig(src string, cfg *Config) |
Reorder with custom config |
File(file *dst.File) |
Reorder a parsed DST file in place |
FileWithConfig(file *dst.File, cfg *Config) |
Reorder parsed file with config |
DefaultConfig() |
Get default configuration |
LoadConfig(path string) |
Load config from TOML file |
FindConfig(startDir string) |
Discover config file walking up directories |
AnalyzeSectionOrder(src string) |
Analyze current section order without modifying |
Without a config file, declarations are ordered as:
- Imports
- main() function
- init() functions (original order preserved)
- Exported constants
- Exported enums (type + iota block + methods)
- Exported variables
- Exported types (type + constructors + methods)
- Exported functions
- Unexported constants
- Unexported enums
- Unexported variables
- Unexported types
- Unexported functions
- Uncategorized (catch-all)
Within each section, declarations are sorted alphabetically. Types group their constructors (functions matching New*TypeName) and methods together.
go-reorder --init # Create config
go-reorder -d ./... # Preview changes
go-reorder -w ./... # Apply changesgo-reorder -c ./... # Exit 1 if changes needed[sections]
order = [
"imports",
"exported_types", # Models first
"unexported_types",
"unexported_funcs",
"exported_funcs", # Handlers last
"uncategorized",
]- Import ordering - Use
goimportsorgcifor that - Code formatting - Use
gofmtorgofumpt - Linting - Use
golangci-lint - Build constraint handling - Files with
//go:buildare processed normally - cgo export comments -
//exportcomments are preserved but not specially handled - Cross-file analysis - Each file is processed independently
Your config doesn't include a section for some code. Options:
- Add the missing section to your config's
orderarray - Add
uncategorizedto catch everything else - Use
mode = "append"to be lenient
The ./... pattern uses Go's package discovery. For non-Go directories, specify paths explicitly:
go-reorder -w ./scripts/*.go ./tools/*.goUse -v (verbose) to see which config is loaded:
go-reorder -v -c ./...
# Output: config: /path/to/.go-reorder.toml
# Or: config: using defaults- Check your config file syntax (TOML)
- Verify section names are spelled correctly
- Use
-dto see what would change before-w
Constructors must match the pattern New*TypeName where TypeName is exact. Examples:
NewUsermatchesUserNewMockUsermatchesUserCreateUserdoes NOT match (noNewprefix)
MIT - See LICENSE file for details.
Built with dave/dst for AST manipulation that preserves comments.
Originally developed as part of the imptest project.
