Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: golines formatter #5432

Merged
merged 4 commits into from
Feb 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ require (
github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a
github.com/golangci/go-printf-func-name v0.1.0
github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d
github.com/golangci/golines v0.0.0-20250217134842-442fd0091d95
github.com/golangci/misspell v0.6.0
github.com/golangci/plugin-module-register v0.1.1
github.com/golangci/revgrep v0.8.0
Expand Down Expand Up @@ -140,6 +141,7 @@ require (
github.com/ccojocar/zxcvbn-go v1.0.2 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/chavacava/garif v0.1.0 // indirect
github.com/dave/dst v0.27.3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/ebitengine/purego v0.8.2 // indirect
github.com/ettle/strcase v0.2.0 // indirect
Expand Down
8 changes: 8 additions & 0 deletions go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 27 additions & 0 deletions jsonschema/golangci.next.jsonschema.json
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,7 @@
"gocyclo",
"godot",
"godox",
"golines",
"err113",
"gofmt",
"gofumpt",
Expand Down Expand Up @@ -1592,6 +1593,32 @@
}
}
},
"golines": {
"type": "object",
"additionalProperties": false,
"properties": {
"max-len": {
"type": "integer",
"default": 100
},
"tab-len": {
"type": "integer",
"default": 4
},
"shorten-comments": {
"type": "boolean",
"default": false
},
"reformat-tags": {
"type": "boolean",
"default": true
},
"chain-split-dots": {
"type": "boolean",
"default": true
}
}
},
"interfacebloat": {
"type": "object",
"additionalProperties": false,
Expand Down
15 changes: 15 additions & 0 deletions pkg/config/formatters_settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,20 @@ var defaultFormatterSettings = FormatterSettings{
Sections: []string{"standard", "default"},
SkipGenerated: true,
},
GoLines: GoLinesSettings{
MaxLen: 100,
TabLen: 4,
ReformatTags: true,
ChainSplitDots: true,
},
}

type FormatterSettings struct {
Gci GciSettings `mapstructure:"gci"`
GoFmt GoFmtSettings `mapstructure:"gofmt"`
GoFumpt GoFumptSettings `mapstructure:"gofumpt"`
GoImports GoImportsSettings `mapstructure:"goimports"`
GoLines GoLinesSettings `mapstructure:"golines"`
}

type GciSettings struct {
Expand Down Expand Up @@ -50,3 +57,11 @@ type GoFumptSettings struct {
type GoImportsSettings struct {
LocalPrefixes string `mapstructure:"local-prefixes"`
}

type GoLinesSettings struct {
MaxLen int `mapstructure:"max-len"`
TabLen int `mapstructure:"tab-len"`
ShortenComments bool `mapstructure:"shorten-comments"`
ReformatTags bool `mapstructure:"reformat-tags"`
ChainSplitDots bool `mapstructure:"chain-split-dots"`
}
41 changes: 41 additions & 0 deletions pkg/goformatters/golines/golines.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package golines

import (
"github.com/golangci/golines"

"github.com/golangci/golangci-lint/pkg/config"
)

const Name = "golines"

type Formatter struct {
shortener *golines.Shortener
}

func New(settings *config.GoLinesSettings) *Formatter {
options := golines.ShortenerConfig{}

if settings != nil {
options = golines.ShortenerConfig{
MaxLen: settings.MaxLen,
TabLen: settings.TabLen,
KeepAnnotations: false, // golines debug (not usable inside golangci-lint)
ShortenComments: settings.ShortenComments,
ReformatTags: settings.ReformatTags,
IgnoreGenerated: false, // handle globally
DotFile: "", // golines debug (not usable inside golangci-lint)
ChainSplitDots: settings.ChainSplitDots,
BaseFormatterCmd: "go fmt", // fake cmd
}
}

return &Formatter{shortener: golines.NewShortener(options)}
}

func (*Formatter) Name() string {
return Name
}

func (f *Formatter) Format(_ string, src []byte) ([]byte, error) {
return f.shortener.Shorten(src)
}
8 changes: 7 additions & 1 deletion pkg/goformatters/meta_formatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/golangci/golangci-lint/pkg/goformatters/gofmt"
"github.com/golangci/golangci-lint/pkg/goformatters/gofumpt"
"github.com/golangci/golangci-lint/pkg/goformatters/goimports"
"github.com/golangci/golangci-lint/pkg/goformatters/golines"
"github.com/golangci/golangci-lint/pkg/logutils"
)

Expand Down Expand Up @@ -50,6 +51,11 @@ func NewMetaFormatter(log logutils.Log, cfg *config.Formatters, runCfg *config.R
m.formatters = append(m.formatters, formatter)
}

// golines calls `format.Source()` internally so no need to format after it.
if slices.Contains(cfg.Enable, golines.Name) {
m.formatters = append(m.formatters, golines.New(&cfg.Settings.GoLines))
}

return m, nil
}

Expand Down Expand Up @@ -80,5 +86,5 @@ func (m *MetaFormatter) Format(filename string, src []byte) []byte {
}

func IsFormatter(name string) bool {
return slices.Contains([]string{gofmt.Name, gofumpt.Name, goimports.Name, gci.Name}, name)
return slices.Contains([]string{gofmt.Name, gofumpt.Name, goimports.Name, gci.Name, golines.Name}, name)
}
28 changes: 28 additions & 0 deletions pkg/golinters/golines/golines.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package golines

import (
"golang.org/x/tools/go/analysis"

"github.com/golangci/golangci-lint/pkg/config"
"github.com/golangci/golangci-lint/pkg/goanalysis"
"github.com/golangci/golangci-lint/pkg/goformatters"
golinesbase "github.com/golangci/golangci-lint/pkg/goformatters/golines"
"github.com/golangci/golangci-lint/pkg/golinters/internal"
)

const linterName = "golines"

func New(settings *config.GoLinesSettings) *goanalysis.Linter {
a := goformatters.NewAnalyzer(
internal.LinterLogger.Child(linterName),
"Checks if code is formatted, and fixes long lines",
golinesbase.New(settings),
)

return goanalysis.NewLinter(
a.Name,
a.Doc,
[]*analysis.Analyzer{a},
nil,
).WithLoadMode(goanalysis.LoadModeSyntax)
}
19 changes: 19 additions & 0 deletions pkg/golinters/golines/golines_integration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package golines

import (
"testing"

"github.com/golangci/golangci-lint/test/testshared/integration"
)

func TestFromTestdata(t *testing.T) {
integration.RunTestdata(t)
}

func TestFix(t *testing.T) {
integration.RunFix(t)
}

func TestFixPathPrefix(t *testing.T) {
integration.RunFixPathPrefix(t)
}
83 changes: 83 additions & 0 deletions pkg/golinters/golines/testdata/fix/in/golines.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
//golangcitest:args -Egolines
//golangcitest:expected_exitcode 0
package testdata

import "fmt"

var (
abc = []string{"a really long string", "another really long string", "a third really long string", "a fourth really long string", fmt.Sprintf("%s %s %s %s >>>>> %s %s", "first argument", "second argument", "third argument", "fourth argument", "fifth argument", "sixth argument")}
)

type MyStruct struct {
aLongProperty int `help:"This is a really long string for this property"`
anotherLongProperty int `help:"This is a really long string for this property, part 2"`
athirdLongProperty int `help:"This is a really long string for this property, part 3...."`
}

type MyInterface interface {
aReallyLongFunctionName(argument1 string, argument2 string, argument3 string, argument4 string, argument5 string, argument6 string) (string, error)
}

// Something here

// Another comment
// A third comment
// This is a really really long comment that needs to be split up into multiple lines. I don't know how easy it will be to do, but I think we can do it!
func longLine(aReallyLongName string, anotherLongName string, aThirdLongName string) (string, error) {
argument1 := "argument1"
argument2 := "argument2"
argument3 := "argument3"
argument4 := "argument4"

fmt.Printf("This is a really long string with a bunch of arguments: %s %s %s %s >>>>>>>>>>>>>>>>>>>>>>", argument1, argument2, argument3, argument4)
fmt.Printf("This is a short statement: %d %d %d", 1, 2, 3)

z := argument1 + argument2 + fmt.Sprintf("This is a really long statement that should be broken up %s %s %s", argument1, argument2, argument3)

fmt.Printf("This is a really long line that can be broken up twice %s %s", fmt.Sprintf("This is a really long sub-line that should be broken up more because %s %s", argument1, argument2), fmt.Sprintf("A short one %d", 3))

fmt.Print("This is a function with a really long single argument. We want to see if it's properly split")

fmt.Println(z)

// This is a really long comment on an indented line. Do you think we can split it up or should we just leave it as is?
if argument4 == "5" {
return "", fmt.Errorf("a very long query with ID %d failed. Check Query History in AWS UI", 12341251)
}

go func() {
fmt.Printf("This is a really long line inside of a go routine call. It should be split if at all possible.")
}()

if "hello this is a big string" == "this is a small string" && "this is another big string" == "this is an even bigger string >>>" {
fmt.Print("inside if statement")
}

fmt.Println(map[string]string{"key1": "a very long value", "key2": "a very long value", "key3": "another very long value"})

return "", nil
}

func shortFunc(a int, b int) error {
c := make(chan int)

for {
select {
case <-c:
switch a {
case 1:
return fmt.Errorf("This is a really long line that can be broken up twice %s %s", fmt.Sprintf("This is a really long sub-line that should be broken up more because %s %s", "xxxx", "yyyy"), fmt.Sprintf("A short one %d", 3))
case 2:
}
}

break
}

if a > 5 {
panic(fmt.Sprintf(">>>>>>>>>>>>>>>>>>> %s %s %s %s", "really long argument", "another really long argument", "a third really long arguement", abc[1:2]))
}

return nil
// This is an end decoration
}
Loading
Loading