A golang formatter that fixes long lines

Circle CI Go Report Card GoDoc Coverage

golines

Golines is a golang formatter that shortens long lines, in addition to all of the formatting fixes done by gofmt.

Motivation

The standard golang formatting tools (gofmt, goimports, etc.) are great, but deliberately don't shorten long lines; instead, this is an activity left to developers.

While there are different tastes when it comes to line lengths in go, we've generally found that very long lines are more difficult to read than their shortened alternatives. As an example:

myMap := map[string]string{"first key": "first value", "second key": "second value", "third key": "third value", "fourth key": "fourth value", "fifth key": "fifth value"}

vs.

myMap := map[string]string{
	"first key": "first value",
	"second key": "second value",
	"third key": "third value",
	"fourth key": "fourth value",
	"fifth key": "fifth value",
}

We built golines to give go developers the option to automatically shorten long lines, like the one above, according to their preferences.

More background and technical details are available in this blog post.

Examples

See this before and after view of a file with very long lines. More example pairs can be found in the _fixtures directory.

Usage

First, install the tool:

go get -u github.com/segmentio/golines

Then, run:

golines [paths to format]

The paths can be either directories or individual files. If no paths are provided, then input is taken from stdin (as with gofmt).

By default, the results are printed to stdout. To overwrite the existing files in place, use the -w flag.

Options

Some other options are described in the sections below. Run golines --help to see all available flags and settings.

Line length settings

By default, the tool tries to shorten lines that are longer than 100 columns and assumes that 1 tab = 4 columns. The latter can be changed via the -m and -t flags respectively.

Dry-run mode

Running the tool with the --dry-run flag will show pretty, git-style diffs via an embedded Python script.

Comment shortening

Shortening long comment lines is harder than shortening code because comments can have arbitrary structure and format. golines includes some basic logic for shortening single-line (i.e., //-prefixed) comments, but this is turned off by default since the quality isn't great. To enable this feature anyway, run with the --shorten-comments flag.

Custom formatters

By default, the tool will use goimports as the base formatter (if found), otherwise it will revert to gofmt. An explicit formatter can be set via the --base-formatter flag.

Generated files

By default, the tool will not format any files that look like they're generated. If you want to reformat these too, run with the --no-ignore-generated flag.

Struct tag reformatting

In addition to shortening long lines, the tool also aligns struct tag keys; see the associated before and after examples in the _fixtures directory. To turn this behavior off, run with --no-reformat-tags.

Developer Tooling Integration

vim-go

Add the following lines to your vimrc, substituting 128 with your preferred line length:

let g:go_fmt_command = "golines"
let g:go_fmt_options = {
    \ 'golines': '-m 128',
    \ }

Visual Studio Code

  1. Install the Run on Save extension
  2. Go into the VSCode settings menu, scroll down to the section for the "Run on Save" extension, click the "Edit in settings.json" link
  3. Set the emeraldwalk.runonsave key as follows (adding other flags to the golines command as desired):
    "emeraldwalk.runonsave": {
        "commands": [
            {
                "match": "\\.go$",
                "cmd": "golines ${file} -w"
            }
        ]
    }
  1. Save the settings and restart VSCode

Others

Coming soon.

How It Works

For each input source file, golines runs through the following process:

  1. Read the file, break it into lines
  2. Add a specially-formatted annotation (comment) to each line that's longer than the configured maximum
  3. Use Dave Brophy's excellent decorated syntax tree library to parse the code plus added annotations
  4. Do a depth-first traversal of the resulting tree, looking for nodes that have an annotation on them
  5. If a node is part of a line that's too long, shorten it by altering the newlines around the node and/or its children
  6. Repeat steps 2-5 until no more shortening can be done
  7. Run the base formatter (e.g., gofmt) over the results, write these to either stdout or the source file

See this blog post for more technical details.

Limitations

The tool has been tested on a variety of inputs, but it's not perfect. Among other examples, the handling of long lines in comments could be improved. If you see anything particularly egregious, please report via an issue.

Comments
  • Change syscall to os in terminal check for Windows compatibility

    Change syscall to os in terminal check for Windows compatibility

    This is a fix for: https://github.com/segmentio/golines/issues/54 I believe this is the preferred approach to ensure cross-platform compatibility. Things are working for me now in Windows but I don't have a way of verifying things in another OS.

  • Break down long method chained calls

    Break down long method chained calls

    Best described by an example: rc.Logger.WithField("FIELD", *someField).Info("Some Very Long Info Here")

    could be possibly broken down to: rc.Logger. WithField("FIELD", *someField). Info("Some Very Long Info Here")

    OR

    rc. Logger. WithField("FIELD", *someField). Info("Some Very Long Info Here")

    Formatting is not working, but essentially move the methods chain one per line.

  • fails with exit code 2 and no error message

    fails with exit code 2 and no error message

    Hi, I have a short file that causes golines to abruptly fail without indication what went wrong.

    Here is the file - https://gist.github.com/hellt/bf38f4b294a567157d5edfa271349dda

    Any clues on what causes it to fail?

  • Formatting for very long if statement doesn't work as expected

    Formatting for very long if statement doesn't work as expected

    golines just moved the last part of my if statement to another line, but other statements still on the same line (and it is still extremely long)

    Attaching screenshot that I got using dry-run зображення

  • Generic type parameters are stripped

    Generic type parameters are stripped

    golines does not yet support generic type parameters.

    Adapting from the example in the official generics tutorial:

    func SumIntsOrFloats[K comparable, V int64 | float64](m map[K]V, longArgument1 int, longArgument2 int, longArgument3 int) V {
    	var s V
    	for _, v := range m {
    		s += v
    	}
    	return s
    }
    

    shortens to:

    func SumIntsOrFloats(
    	m map[K]V,
    	longArgument1 int,
    	longArgument2 int,
    	longArgument3 int,
    ) V {
    	var s V
    	for _, v := range m {
    		s += v
    	}
    	return s
    }
    

    which is missing the bracketed type parameters and thus is incorrect.

    We depend on https://github.com/dave/dst to generate the ASTs that are used in the shortening process, but unfortunately that library won't be fixed to support generics for a while because the author is currently off-the-grid (see https://github.com/dave/dst/issues/63 for details). I'll follow that issue, and when it's fixed we should be able to fix golines as well.

    Apologies for any inconvenience caused here.

  • Generic type parameters stripped, if file includes struct tags with more than one key

    Generic type parameters stripped, if file includes struct tags with more than one key

    If the file includes any struct tags possibly needing alignment (meaning there are at least 2 tags on a field), generic parameters from a function later in the file are stripped.

    I have confirmed this is not an issue in the backend formatter with both gofmt and goimports.

    Example

    Passing

    package strips_generics
    
    type needsTagAllignemnt struct {
            Foo  *bool   `json:"foo" db:"foo"`
    }
    
    
    // Has its type parameters stripped
    func setDefault[T any](dst **T, val T) {
            if *dst == nil {
                    *dst = &val
            }
    }
    

    into golines with golines ./main.go outputs

    package strips_generics
    
    type needsTagAllignemnt struct {
            Foo *bool `json:"foo" db:"foo"`
    }
    
    // Has its type parameters stripped
    func setDefault(dst **T, val T) {
            if *dst == nil {
                    *dst = &val
            }
    }
    
    

    Code with accompanying go.mod: test.zip

  • Specifying base-formatter causes golines to not shorten anything

    Specifying base-formatter causes golines to not shorten anything

    Running this with latest golines:

    golines --base-formatter "cat test.go" -m 120 test.go
    

    Where test.go contains:

    package main
    
    import "fmt"
    
    func MyLongFunction(one, two, three, four, five, six, seven, eight, nine, ten, eleven string) {
    	fmt.Println("Hello world!")
    }
    
    $ golines --base-formatter "cat test.go" -m 50 test.go 
    package main
    
    import "fmt"
    
    func MyLongFunction(one, two, three, four, five, six, seven, eight, nine, ten, eleven string) {
            fmt.Println("Hello world!")
    }
    $ golines -m 50 test.go 
    package main
    
    import "fmt"
    
    func MyLongFunction(
            one, two, three, four, five, six, seven, eight, nine, ten, eleven string,
    ) {
            fmt.Println("Hello world!")
    }
    

    Naturally one would specify an actually useful base-formatter, but this is just to make the case.

  • Fix formatting for very long binary expressions

    Fix formatting for very long binary expressions

    Issue

    I found a problem that if we have a long and complex expression, then golines will add a new line only before last operand. So if you have a code like this:

    if getBooool1() || getBooool2() || getBooool3() || getBooool4() || getBooool5() || getBooool6() || getBooool7() {
    	return "Hey"
    }
    

    it will become

    if getBooool1() || getBooool2() || getBooool3() || getBooool4() || getBooool5() || getBooool6() ||
            getBooool7() {
    	return "Hey"
    }
    

    Solution

    My fix is pretty simple. The result will look like this

    if getBooool1() || getBooool2() || getBooool3() || getBooool4() || getBooool5() ||
            getBooool6() ||
            getBooool7() {
    	return "Hey"
    }
    

    It is not perfect. I would like to see something like this:

    if getBooool1() || getBooool2() || getBooool3() || getBooool4() || getBooool5() ||
            getBooool6() || getBooool7() {
    	return "Hey"
    }
    

    but this will require much more changes and really big refactoring I think. It is because how complex BinaryExpr are treated by dst

  • Multiple tags with one containing `:`s

    Multiple tags with one containing `:`s

    The below code works as expected:

    package pkg
    
    type MyStruct struct {
    	Field1 string `url:"http://username:[email protected]:1234"`
    }
    

    No changes:

    golines --dry-run ./pkg/foo.go
    // No output
    

    The code below does NOT work as expected if I add another tag in front of or behind the url tag:

    package pkg
    
    type MyStruct struct {
    	Field1 string `param:"FOOBAR" url:"http://username:[email protected]:1234"`
    }
    
    $ golines --dry-run ./pkg/foo.go
    
    --- ./pkg/foo.go
    +++ ./pkg/foo.go.shortened
    @@ -1,6 +1,6 @@
     package pkg
     type MyStruct struct {
    -	Field1 string `param:"FOOBAR" url:"http://username:[email protected]:1234"`
    +	Field1 string `param:"FOOBAR" url:"http://username:[email protected]:1234" http:"" username:"" bar:""`
     }
    

    Also if the command above does align the tags, what does adding --reformat-tags do?

  • Indent tag declaration beginnings of embedded structs

    Indent tag declaration beginnings of embedded structs

    Here is an example: MyStruct2's tags are not aligned with other fields of MyStruct. It will be nice if tag declaration beginnings were aligned too.

    package fixtures
    
    type MyStruct struct {
    	Field1    string `json:"field3" tag:"something"`
    	Field2    string `              tag:"something else"`
    	MyStruct2 `json:"ms2"    tag:"tada"`
    }
    
    type MyStruct2 struct {
    	Field3 string `json:"field3"`
    }
    
  • Error installing v0.10.0

    Error installing v0.10.0

    Seems like the github.com/dave/[email protected] module isn't compiling properly. Strangely, I only see this error in Github Actions, but not on my Mac. I'm wondering if it's a Linux issue?

    Update: I cannot reproduce in a Docker container on my Mac, so probably not a Linux issue. Am I doing something stupid?

    $ go install github.com/segmentio/[email protected]
    go: downloading github.com/segmentio/golines v0.10.0
    go: downloading github.com/dave/dst v0.27.0
    go: downloading github.com/fatih/structtag v1.2.0
    go: downloading github.com/pmezard/go-difflib v1.0.0
    go: downloading github.com/sirupsen/logrus v1.4.2
    go: downloading github.com/x-cray/logrus-prefixed-formatter v0.5.2
    go: downloading golang.org/x/crypto v0.0.0-20220517005047-85d78b3ac167
    go: downloading gopkg.in/alecthomas/kingpin.v2 v2.2.6
    go: downloading golang.org/x/tools v0.1.10
    go: downloading github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b
    go: downloading golang.org/x/sys v0.0.0-20220318055525-2edf467146b5
    go: downloading github.com/alecthomas/template v0.0.0-201907180[12](https://github.com/weberc2/mono/runs/6804449321?check_suite_focus=true#step:5:13)654-fb15b899a751
    go: downloading github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d
    go: downloading golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1
    go: downloading github.com/mattn/go-colorable v0.1.4
    go: downloading github.com/mattn/go-isatty v0.0.10
    go: downloading golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
    go: downloading golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3
    # github.com/dave/dst/decorator
    Error: /go/pkg/mod/github.com/dave/[email protected]/decorator/decorator-fragment-generated.go:620:12: n.Type.TypeParams undefined (type *ast.FuncType has no field or method TypeParams)
    Error: /go/pkg/mod/github.com/dave/[email protected]/decorator/decorator-fragment-generated.go:621:29: n.Type.TypeParams undefined (type *ast.FuncType has no field or method TypeParams)
    Error: /go/pkg/mod/github.com/dave/[email protected]/decorator/decorator-fragment-generated.go:625:12: n.Type.TypeParams undefined (type *ast.FuncType has no field or method TypeParams)
    Error: /go/pkg/mod/github.com/dave/[email protected]/decorator/decorator-fragment-generated.go:692:7: n.TypeParams undefined (type *ast.FuncType has no field or method TypeParams)
    Error: /go/pkg/mod/github.com/dave/[email protected]/decorator/decorator-fragment-generated.go:693:24: n.TypeParams undefined (type *ast.FuncType has no field or method TypeParams)
    Error: /go/pkg/mod/github.com/dave/[email protected]/decorator/decorator-fragment-generated.go:697:7: n.TypeParams undefined (type *ast.FuncType has no field or method TypeParams)
    Error: /go/pkg/mod/github.com/dave/[email protected]/decorator/decorator-fragment-generated.go:9[13](https://github.com/weberc2/mono/runs/6804449321?check_suite_focus=true#step:5:14):8: undefined: ast.IndexListExpr
    Error: /go/pkg/mod/github.com/dave/[email protected]/decorator/decorator-fragment-generated.go:[14](https://github.com/weberc2/mono/runs/6804449321?check_suite_focus=true#step:5:15)40:7: n.TypeParams undefined (type *ast.TypeSpec has no field or method TypeParams)
    Error: /go/pkg/mod/github.com/dave/[email protected]/decorator/decorator-fragment-generated.go:1441:24: n.TypeParams undefined (type *ast.TypeSpec has no field or method TypeParams)
    Error: /go/pkg/mod/github.com/dave/[email protected]/decorator/decorator-fragment-generated.go:1445:7: n.TypeParams undefined (type *ast.TypeSpec has no field or method TypeParams)
    Error: /go/pkg/mod/github.com/dave/[email protected]/decorator/decorator-fragment-generated.go:1445:7: too many errors
    note: module requires Go 1.[18](https://github.com/weberc2/mono/runs/6804449321?check_suite_focus=true#step:5:19)
    Error: Process completed with exit code 2.
    
  • Backslashes being removed from struct tag

    Backslashes being removed from struct tag

    I have the following struct tag: jsonschema:"title=Title,minLength=1,maxLength=40,pattern=^[^\\-<>%{}\\[\\]&#\\\\\\\"]+$", and upon running golines ${file} -w -m 120 using the Run on Save extension in VSCode, the tag is changed into jsonschema:"title=title=Title,maxLength=40,pattern=^[^\-<>%{}\[\]&#\\\"]+$", turning it into an invalid struct tag.

  • Improve ignore capabilities

    Improve ignore capabilities

    Currently there are two flags for ignoring files: --ignore-generated and --ignored-dirs.

    --ignore-generated is very strict about what it considers a generated file, causing a lot of generated files to be considered not generated in many code bases.

    --ignored-dirs only checks the last part of a path which can mean it will match a much larger set that intended if the ignored directory has a generic name. Additionally it is rather combersome to specify multile directories as the flag needs to be repeated and it is quite long (granted not a major concern).

    Ideally I would like to see ignores specified as regular expressions on the whole path, similar to how golangci-lint handles its skip-dirs configuration

  • Do not move all parameters to individual lines

    Do not move all parameters to individual lines

    If a line exceeds 80 characters, we move all the parameters to each line on their own. Instead we could try to fit them in as lesser lines as possible. This would help avoid scrolling, improves readability. It is fine, even if this takes time.

    In the screenshot below, the 3rd segment is more readable than the 2nd segment.

    Screenshot from 2022-11-13 14-46-55

  • refactor: Create shorten package

    refactor: Create shorten package

    Description

    Refactor codebase to add a shorten package. This allows other projects to use this library and closes #34.

    Some notes:

    • Sometimes a dir called pkg is preferred for things that should be public. I could easily move to pkg/shorten. I opted to keep the imports shorter, but let me know if you have a different preference!
    • Most of the funcs in the shorten package could be moved to their own packages in internal. Again, I don't mind doing the refactoring if you'd prefer that.
    • To simplify usage in other projects, I have added shorten.DefaultConfig() which returns a shorten.Config struct that matches the default flag values. I have also changed shorten.New() so that config is an optional parameter. If not passed, the default config is used.
    • I renamed most funcs and vars to make them private. Only shorten.New(), shorten.DefaultConfig(), and Shortener.Shorten() are exposed.
    • All tests are passing.

    Unfortunately, this PR required some major refactoring and will create merge conflicts with any other open PRs.

    Let me know if you have any suggestions!

    Example: Usage in another project

    main.go
    package main
    
    import (
    	"fmt"
    	"github.com/segmentio/golines/shorten"
    )
    
    var code = `package main
    
    var myMap = map[string]string{"key1": "value1", "key2": "value2", "key3": "value3", "key4": "value4", "key5", "value5"}
    `
    
    
    func main() {
    	shortener := shorten.New()
    
    	out, err := shortener.Shorten([]byte(code))
    	if err != nil {
    		panic(err)
    	}
    
    	fmt.Print(string(out))
    }
    
    

    Todo

    • Add a quick section to the readme that shows usage from other packages.
  • added gqlgen generated term

    added gqlgen generated term

    One of the files that gqlgen generates has the comment:

    // This file will be automatically regenerated based on the schema, any resolver implementations
    // will be copied through when generating and any unknown code will be moved to the end.
    

    so figured I'd add it to generatedTerms to avoid issues between gqlgeng and golines.

Find outdated golang packages
Find outdated golang packages

This project is not supported anymore Go-outdated is minimalistic library that helps to find outdated packages hosted on github.com in your golang pro

Sep 27, 2022
Golang AST visualizer
Golang AST visualizer

GoAst Viewer Golang AST visualizer. Demo GoAst Viewer Demo Usage You just need to run a http server and open your browser to index.html Installation T

Dec 29, 2022
PlantUML Class Diagram Generator for golang projects
PlantUML Class Diagram Generator for golang projects

GoPlantUML PlantUML Class Diagram Generator for golang projects. Generates class diagram text compatible with plantuml with the information of all str

Dec 31, 2022
A Golang tool that does static analysis, unit testing, code review and generate code quality report.
A Golang tool that does static analysis, unit testing, code review and generate code quality report.

goreporter A Golang tool that does static analysis, unit testing, code review and generate code quality report. This is a tool that concurrently runs

Jan 8, 2023
Know when GC runs from inside your golang code

gcnotifier gcnotifier provides a way to receive notifications after every run of the garbage collector (GC). Knowing when GC runs is useful to instruc

Dec 26, 2022
a simple golang SSA viewer tool use for code analysis or make a linter
a simple golang SSA viewer tool use for code analysis or make a linter

ssaviewer A simple golang SSA viewer tool use for code analysis or make a linter ssa.html generate code modify from src/cmd/compile/internal/ssa/html.

May 17, 2022
The Golang linter that checks that there is no simultaneous return of `nil` error and an invalid value.

nilnil Checks that there is no simultaneous return of nil error and an invalid value. Installation & usage $ go install github.com/Antonboom/nilnil@la

Dec 14, 2022
API em Golang utilizando clean architecture

Clean Arch in Go Read about Clean Architecture Build make Run tests make te

Dec 23, 2021
golang long polling library. Makes web pub-sub easy via HTTP long-poll server :smiley: :coffee: :computer:
golang long polling library.  Makes web pub-sub easy via HTTP long-poll server :smiley: :coffee: :computer:

golongpoll Golang long polling library. Makes web pub-sub easy via an HTTP long-poll server. New in v1.1 Deprecated CreateManager and CreateCustomMana

Jan 6, 2023
eac3to wrapper that fixes its bug 288

eac3to-wrapper eac3to-wrapper aims to fix eac3to's long standing bug 288. To build, first install Go from golang.org/dl, then: go build -ldflags "-X m

Jun 7, 2022
Quick and simple Go application that fixes updates on non TPM, UEFI devices on the latest insider builds.
Quick and simple Go application that fixes updates on non TPM, UEFI devices on the latest insider builds.

W11-Updater Quick and simple Go application that fixes updates on non TPM, UEFI devices on the latest insider builds. Sick of this bullshit? Build Ins

Oct 21, 2021
Console JSON formatter with query feature
Console JSON formatter with query feature

Console JSON formatter with query feature. Install: $ go get github.com/miolini/jsonf Usage: Usage of jsonf: -c=true: colorize output -d=false: de

Dec 4, 2022
A shell parser, formatter, and interpreter with bash support; includes shfmt

sh A shell parser, formatter, and interpreter. Supports POSIX Shell, Bash, and mksh. Requires Go 1.14 or later. Quick start To parse shell scripts, in

Dec 29, 2022
A shell parser, formatter, and interpreter with bash support; includes shfmt

sh A shell parser, formatter, and interpreter. Supports POSIX Shell, Bash, and mksh. Requires Go 1.14 or later. Quick start To parse shell scripts, in

Jan 8, 2023
Small Clojure interpreter, linter and formatter.
Small Clojure interpreter, linter and formatter.

Joker is a small Clojure interpreter, linter and formatter written in Go. Installation On macOS, the easiest way to install Joker is via Homebrew: bre

Dec 30, 2022
go-playground-converter is formatter error response inspiration like express-validator in nodejs build on top go-playground-validator.

Go Playground Converter go-playground-converter is formatter error response inspiration like express-validator in nodejs build on top in go-playground

Dec 9, 2022
groqfmt is a formatter for the GROQ query language

groqfmt groqfmt is a formatter for the GROQ query language. Usage Either: groqfmt INPUT > OUTPUT or: cat INPUT | groqfmt > OUTPUT or cat INPUT | groqf

Oct 6, 2022
tabitha is a no-frills tabular formatter for the terminal.

tabitha tabitha is a no-frills tabular formatter for the terminal. Features Supports padding output to the longest display text, honoring ANSI colors

Aug 19, 2022
axmlfmt is an opinionated formatter for Android XML resources

axmlfmt axmlfmt is an opinionated formatter for Android XML resources. It takes XML that looks like <?xml version="1.0" encoding="utf-8"?> <LinearLayo

May 14, 2022
Opionated sql formatter for use with .go files containing backticked queries

fumpt-the-sql Opionated sql formatter for use with .go files containing backticked queries. Uses https://sqlformat.darold.net/ for the actual sql form

Dec 10, 2021