Skip to content

hefa/gocondense

 
 

Repository files navigation

gocondense

Go Reference Codecov CI Go Report Card

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.

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

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 // kept
x := variable
y := 42
z := string("foo")
unary := -value
result := (a + b) * c // kept
Trim 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
2
a := 1

var b =
// comment
2
Collapse 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 effects
Remove 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 { /* ... */ }

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:
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:

autocmd BufWritePre *.go silent execute '%!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.

About

A golang formatter that condenses code

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages

  • Go 99.9%
  • Shell 0.1%