gocondense
A gofmt-compatible formatter that condenses multi-line constructs onto single lines where they fit
About gocondense
gocondense
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. gocondense is a superset of gofmt, so running gofmt
afterwards changes nothing.
Installation
go install github.com/abemedia/gocondense/cmd/gocondense@latest
Usage
gocondense 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 stdout
Files 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 |
Transformations
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
}
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
})
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}
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,
}
Binary expressions, selector chains, and generic type instantiations that span multiple lines are condensed onto a single line. In a mixed-precedence chain, the higher-precedence expression is condensed on its own even when the surrounding chain stays multi-line.
_ = a +
b
_ = obj.
Method
_ = g[
int, string](1, "a")
_ = a + b
_ = obj.Method
_ = g[int, string](1, "a")
Partial condensing in a mixed-precedence chain:
_ = a + // keep
b*
c +
d
_ = a + // keep
b*c +
d
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{}
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{}
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 // kept
x := variable
y := 42
z := string("foo")
unary := -value
result := (a + b) * c // kept
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,
}
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
2
a := 1
var b =
// comment
2
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{}
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}}
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 effects
Unnecessary blank identifiers are removed from range statements.
for i, _ := range s { /* ... */ }
for i := range s { /* ... */ }
And:
for _ = range s { /* ... */ }
for range s { /* ... */ }
Editor Integration
VS Code
Add the following to your settings:
{
"go.formatTool": "custom",
"go.alternateTools": {
"customFormatter": "gocondense"
}
}
GoLand
- 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.
Vim
With vim-go:
let g:go_fmt_command = "gocondense"
Without vim-go, format on save can be configured with an autocommand:
function! s:gocondense() abort
let l:view = winsaveview()
silent execute '%!gocondense'
if v:shell_error
silent undo
endif
call winrestview(l:view)
endfunction
autocmd BufWritePre *.go call s:gocondense()
Neovim
With conform.nvim:
require("conform").setup({
formatters_by_ft = {
go = { "gocondense" },
},
formatters = {
gocondense = {
command = "gocondense",
stdin = true,
},
},
})
Using as a Library
go get github.com/abemedia/gocondense@latest
Format 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.
Frequently Asked Questions
What is gocondense?
gocondense is a Code Analysis library for the Go programming language. A gofmt-compatible formatter that condenses multi-line constructs onto single lines where they fit
How do I install gocondense?
Install gocondense with the Go module system using `go get abemedia/gocondense`. Check the repository for the current installation instructions.
What category does gocondense belong to?
gocondense is listed under Code Analysis, specifically Code Analysis.