Skip to content
/ wormatter Public

Comprehensive opinionated formatter for Go

License

Notifications You must be signed in to change notification settings

werf/wormatter

Repository files navigation

Wormatter

A DST-based Go source code formatter. Highly opinionated, but very comprehensive. Gofumpt and gci built-in.

Installation

Download the latest binary from GitHub Releases.

Usage

wormatter <file.go|directory>

Formats Go files in place. Recursively processes directories.

Options

  • -c, --check — Check if files need formatting without modifying them. Exits with code 1 if any file needs formatting.
  • -e, --exclude <pattern> — Exclude files matching glob pattern (can be specified multiple times).

Examples

# Format a single file
wormatter main.go

# Format all Go files in a directory
wormatter ./pkg/

# Check if files are formatted (useful for CI)
wormatter --check .

# Exclude test files
wormatter --exclude "*_test.go" .

# Exclude multiple patterns
wormatter --exclude "*.pb.go" --exclude "vendor/*" .

Free-Floating Comments

Files with free-floating comments (section headers separated from code by a blank line) on var or const declarations will produce an error, since these comments cannot be safely preserved during declaration merging and reordering. Normal doc comments (directly attached to the declaration) are preserved correctly.

Generated Files

Files starting with any of these comments are automatically skipped:

  • // Code generated
  • // DO NOT EDIT
  • // GENERATED
  • // Autogenerated
  • // auto-generated
  • // Automatically generated

Building

task build

Quick Reference

For a complete before/after example covering all formatting rules, see:

These files are used as the main test fixture and showcase every rule described below: declaration reordering, const/var merging and grouping, struct field sorting, function signature collapsing, spacing normalization, encoding-tag preservation, doc comment preservation, and more.

Formatting Rules

File-Level Declaration Order

Declarations are reordered top to bottom:

Order Declaration Notes
1 Imports Unchanged
2 init() functions Preserved in original order
3 Constants Merged into single const() block
4 Variables Merged into single var() block
5 Types Grouped by category, each followed by its constructors and methods
6 Standalone functions Sorted by exportability, then by architectural layer
7 main() function Always last
Example
// Before
func helper() {}
var name = "app"
type Config struct{}
func main() {}
const version = "1.0"
func init() { setup() }
func NewConfig() *Config { return &Config{} }

// After
const version = "1.0"

var name = "app"

func init() { setup() }

type Config struct{}

func NewConfig() *Config { return &Config{} }

func helper() {}

func main() {}

Constants and Variables

Block format:

  • Single declaration → inline: const X = 1
  • Multiple declarations → parenthesized block: const ( ... )

Grouping (separated by empty lines):

Priority Group Sub-grouping
1 Blank identifiers (var _ Interface = ...) None
2 Public (uppercase) By custom type
3 Private (lowercase) By custom type

Within each group:

  • Constants: typed consts (e.g. Stage, StatusCode) come first, preserving their original order to keep intentional orderings intact; then untyped consts sorted alphabetically.
  • Variables: typed vars (e.g. StatusCode) come first, then untyped. Original order preserved within each sub-group (to avoid breaking initialization dependencies).
Example
// Before
const (
    maxRetries = 3
    StatusOK StatusCode = "ok"
    AppName = "myapp"
    StatusError StatusCode = "error"
    defaultTimeout = 30
    Version = "1.0"
)

// After
const (
    StatusError StatusCode = "error"
    StatusOK    StatusCode = "ok"

    AppName = "myapp"
    Version = "1.0"

    defaultTimeout = 30
    maxRetries     = 3
)

Types

Category order:

Order Category Example
1 Simple types type MyString string, function types
2 Function interfaces Interfaces with exactly 1 method
3 Other interfaces Interfaces with 0 or 2+ methods
4 Structs

Types within each category preserve their original order.

After each type definition:

  1. Constructors (functions starting with New/new that return the type)
  2. Methods (functions with receiver of that type)

Constructor matching for type T:

  • Name starts with New (exported) or new (unexported)
  • Returns T, *T, (T, error), (*T, error), etc.
  • Name suffix matches T case-insensitively, or starts with T + non-lowercase char
Function Type Foo Match?
NewFoo Foo
newFoo foo
NewFooWithOptions Foo
NewFoobar Foo ✗ (matches Foobar)

Sorting:

  • Constructors: alphabetically
  • Methods: exported first, then unexported; each group sorted by architectural layer
Example
// Before
type Server struct {
    port int
}
func (s *Server) Start() {}
func (s *Server) stop() {}
type Handler func(r Request)
func NewServer(port int) *Server { return &Server{port: port} }
func NewServerWithTLS(port int, cert string) *Server { return &Server{port: port} }
func (s *Server) Listen() {}
type Reader interface {
    Read(p []byte) (n int, err error)
}

// After
type Handler func(r Request)

type Reader interface {
    Read(p []byte) (n int, err error)
}

type Server struct {
    port int
}

func NewServer(port int) *Server { return &Server{port: port} }

func NewServerWithTLS(port int, cert string) *Server { return &Server{port: port} }

func (s *Server) Listen() {}

func (s *Server) Start() {}

func (s *Server) stop() {}

Struct Fields

Fields in named type declarations (type Foo struct{...}) are grouped (separated by empty lines):

Order Group Sorting
1 Embedded Alphabetically by type name
2 Public Alphabetically
3 Private Alphabetically

Anonymous structs (table tests, inline struct fields, composite literal types) are not reordered. Structs with encoding-related struct tags (json, yaml, xml, toml, protobuf) are also not reordered, to preserve wire format compatibility.

Struct literals with named fields are reordered to match the struct definition.

Example
// Before
type Server struct {
    port     int
    Logger
    Name     string
    host     string
    Timeout  int
    io.Reader
}

// After
type Server struct {
    io.Reader
    Logger

    Name    string
    Timeout int

    host string
    port int
}
// Struct literal — before
cfg := &Config{debug: true, Timeout: 30, Name: "app"}

// Struct literal — after (matches struct field order)
cfg := &Config{Name: "app", Timeout: 30, debug: true}

Positional literals are automatically converted to keyed literals:

// Before — positional
p := Person{"John", 30}

// After — converted to keyed, then sorted
p := Person{Age: 30, Name: "John"}

This conversion only applies to structs defined in the same file. External struct literals are left unchanged.


Functions

Function signatures are always collapsed to a single line:

// Before — multi-line signature
func Process(
    ctx context.Context,
    input Input,
) (Output, error) {

// After — single line
func Process(ctx context.Context, input Input) (Output, error) {

This applies to function definitions, methods, function types, and interface methods.


Standalone functions sorting:

  1. Exported first, then unexported
  2. Within each group: by architectural layer (high-level first)

Architectural layer — determined by call depth to local functions:

  • Layer 0: calls no local functions (utilities)
  • Layer N: calls functions from layer N-1 or lower
  • Cyclic calls share the same layer

Higher layers appear first (orchestrators → utilities).

Example
// Before
func validate(s string) bool { return len(s) > 0 }
func process(s string) string { return transform(s) }
func transform(s string) string { return strings.ToUpper(s) }
func Run(input string) {
    if validate(input) {
        result := process(input)
        fmt.Println(result)
    }
}

// After — exported first, then by layer (high to low)
func Run(input string) {          // Layer 2: calls validate, process
    if validate(input) {
        result := process(input)
        fmt.Println(result)
    }
}

func process(s string) string {   // Layer 1: calls transform
    return transform(s)
}

func transform(s string) string { // Layer 0: no local calls
    return strings.ToUpper(s)
}

func validate(s string) bool {    // Layer 0: no local calls
    return len(s) > 0
}

Body formatting:

  • Empty body stays one line: func foo() {}
  • Non-empty body expands to multiple lines
  • Empty line before return (unless it's the first statement)
  • Empty line before line comments (unless first in block)
  • No empty lines between case clauses in switch/select
Example
// Before
func process(x int) int {
    result := x * 2
    return result
}

// After — empty line before return
func process(x int) int {
    result := x * 2

    return result
}
// Before
switch x {
case 1:
    return "one"

case 2:
    return "two"
}

// After — no empty lines between cases
switch x {
case 1:
    return "one"
case 2:
    return "two"
}

Spacing

  • Single blank line between major sections, type definitions, and functions
  • Double blank lines compacted to single
  • No blank lines within const/var groups (only between groups)

About

Comprehensive opinionated formatter for Go

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors 3

  •  
  •  
  •