Awesome Go

gocondense

CategoryCode Analysis
SubcategoryCode Analysis
Stars22

A gofmt-compatible formatter that condenses multi-line constructs onto single lines where they fit

About gocondense

gocondense

Go Reference Codecov CI

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.

FlagDescriptionDefault
--max-lenMaximum line length; constructs exceeding this remain on multiple lines80
--tab-widthTab character width used for line length calculation4

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

  1. Open Settings > Tools > File Watchers and add a Custom template.
  2. Configure as follows:
FieldValue
Programgocondense
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.

← Back to Code Analysis