A Go source code formatter that condenses multi-line constructs onto single
lines where they fit, reducing vertical noise while preserving readability.
All transformations are line-length aware (default 80 columns), idempotent, and
preserve all comments.
go install github.com/abemedia/gocondense/cmd/gocondense@latestgocondense file1.go file2.go # format files in-place
gocondense ./ # format all .go files in a directory
gocondense ./... # format all .go files recursively
cat file.go | gocondense # read from stdin, write to stdoutFiles are modified in-place. Generated files, vendor and testdata
directories, as well as paths listed in go.mod ignore directives are skipped
unless explicitly specified as arguments.
| Flag | Description | Default |
|---|---|---|
--max-len |
Maximum line length; constructs exceeding this remain on multiple lines | 80 |
--tab-width |
Tab character width used for line length calculation | 4 |
Condense function signatures
Multi-line parameter lists, return types, and type parameter lists are condensed onto single lines. Each part is condensed independently — for example, parameters with comments remain multi-line while return types are still condensed.
func Add[
T ~int,
](
a T,
b T,
) (
result T,
err error,
) {
return a + b, nil
}func Add[T ~int](a, b T) (result T, err error) {
return a + b, nil
}Partial condensing when parameters contain comments:
func Partial[
T any,
](
a T, // keep
b string,
) (
string,
error,
) {
return "", nil
}func Partial[T any](
a T, // keep
b string,
) (string, error) {
return "", nil
}Condense function calls
Multi-line argument lists are condensed onto a single line. When the last argument is multiline, leading arguments are condensed onto the first line and the closing parenthesis is pulled up. Calls are left untouched if any argument other than the last is multiline.
result := myFunction(
arg1,
arg2,
arg3,
)result := myFunction(arg1, arg2, arg3)Trailing multiline argument:
processData(
people,
func(p Person) bool {
return p.Age >= 18
},
)processData(people, func(p Person) bool {
return p.Age >= 18
})Condense slice, array, and unkeyed struct literals
Slice, array, and unkeyed struct literals are condensed onto a single line, provided all elements are single-line.
numbers := []int{
1,
2,
3,
}numbers := []int{1, 2, 3}Condense keyed struct and map literals
Keyed literals are only condensed when the first element already shares a line with the opening brace.
Condensed (first element on brace line):
p := Person{Name: "John",
Age: 30,
}p := Person{Name: "John", Age: 30}Left untouched (first element on its own line):
p := Person{
Name: "John",
Age: 30,
}Condense expressions
Binary expressions, selector chains, and generic type instantiations that span multiple lines are condensed onto a single line, provided both sides of the expression are single-line.
_ = a +
b
_ = obj.
Method
_ = g[
int, string](1, "a")_ = a + b
_ = obj.Method
_ = g[int, string](1, "a")Unwrap single-item declaration groups
Declaration groups (import, const, var, type) containing a single item
are unwrapped onto a single line without parentheses. Groups with comments
inside are left untouched.
import (
"fmt"
)
const (
x = 1
)
var (
y = 2
)
type (
S struct{}
)import "fmt"
const x = 1
var y = 2
type S struct{}Group adjacent parameters with the same type
Adjacent type parameters or function parameters and results with the same type are merged into a single declaration.
func foo(a int, b int) (c int, d int) { return a, b }
type Pair[A any, B any] struct{}func foo(a, b int) (c, d int) { return a, b }
type Pair[A, B any] struct{}Remove unnecessary parentheses
Redundant parentheses around variables, literals, type conversions, and unary expressions are removed. Parentheses required for precedence are preserved.
x := (variable)
y := (42)
z := (string)("foo")
unary := -(value)
result := (a + b) * c // keptx := variable
y := 42
z := string("foo")
unary := -value
result := (a + b) * c // keptTrim leading and trailing blank lines
Leading and trailing blank lines are removed from all delimited constructs, including function bodies, case clauses, composite literals, struct and interface definitions, parameter lists, and declaration groups.
Function bodies:
func foo() {
bar()
}func foo() {
bar()
}Case clauses:
switch {
case true:
x := 1
}switch {
case true:
x := 1
}Composite literals:
_ = map[string]int{
"a": 1,
}_ = map[string]int{
"a": 1,
}Remove blank lines after assignment operators
Blank lines between an assignment operator (:= or =) and the value are
removed. When a comment appears between the operator and the value, blank lines
are removed but the comment is preserved on its own line.
a :=
1
var b =
// comment
2a := 1
var b =
// comment
2Collapse empty blocks
Empty function bodies, struct definitions, and interface definitions are collapsed onto a single line.
func noop() {
}
type S struct {
}
type I interface {
}func noop() {}
type S struct{}
type I interface{}Elide redundant types in composite literals
Redundant type names in composite literals are removed.
_ = []T{T{1, 2}, T{3, 4}}
_ = []*T{&T{1, 2}, &T{3, 4}}
_ = map[T]T{T{1, 2}: T{3, 4}}_ = []T{{1, 2}, {3, 4}}
_ = []*T{{1, 2}, {3, 4}}
_ = map[T]T{{1, 2}: {3, 4}}Remove redundant len calls and zero bounds in slices
Redundant len calls and zero low bounds are removed from slice expressions.
Three-index slices, expressions with side effects, and explicit non-zero bounds
are left untouched.
_ = s[1:len(s)]
_ = s[0:]_ = s[1:]
_ = s[:]Left untouched:
_ = s[0:2] // explicit non-zero high bound
_ = s[0:len(s):cap(s)] // three-index slice
_ = f()[0:len(f())] // side effectsRemove blank identifiers in range statements
Unnecessary blank identifiers are removed from range statements.
for i, _ := range s { /* ... */ }for i := range s { /* ... */ }And:
for _ = range s { /* ... */ }for range s { /* ... */ }Add the following to your settings:
{
"go.formatTool": "custom",
"go.alternateTools": {
"customFormatter": "gocondense"
}
}- Open Settings > Tools > File Watchers and add a Custom template.
- Configure as follows:
| Field | Value |
|---|---|
| Program | gocondense |
| Arguments | $FilePath$ |
| Output path | $FilePath$ |
| Working directory | $ProjectFileDir$ |
Disable all checkboxes in the Advanced section.
With vim-go:
let g:go_fmt_command = "gocondense"Without vim-go, format on save can be configured with an autocommand:
autocmd BufWritePre *.go silent execute '%!gocondense'With conform.nvim:
require("conform").setup({
formatters_by_ft = {
go = { "gocondense" },
},
formatters = {
gocondense = {
command = "gocondense",
stdin = true,
},
},
})go get github.com/abemedia/gocondense@latestFormat a source file with default settings (80 columns, 4-wide tabs):
formatted, err := gocondense.Source(src)Use a custom configuration:
f := gocondense.New(gocondense.Config{
MaxLen: 120,
TabWidth: 2,
})
formatted, err := f.Source(src)See the Go Reference for full API documentation.
