Skip to content

Commit feddadf

Browse files
authored
feat: golines formatter (#5432)
1 parent 610cc04 commit feddadf

File tree

14 files changed

+480
-2
lines changed

14 files changed

+480
-2
lines changed

go.mod

+2
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ require (
4646
github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a
4747
github.com/golangci/go-printf-func-name v0.1.0
4848
github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d
49+
github.com/golangci/golines v0.0.0-20250217134842-442fd0091d95
4950
github.com/golangci/misspell v0.6.0
5051
github.com/golangci/plugin-module-register v0.1.1
5152
github.com/golangci/revgrep v0.8.0
@@ -140,6 +141,7 @@ require (
140141
github.com/ccojocar/zxcvbn-go v1.0.2 // indirect
141142
github.com/cespare/xxhash/v2 v2.3.0 // indirect
142143
github.com/chavacava/garif v0.1.0 // indirect
144+
github.com/dave/dst v0.27.3 // indirect
143145
github.com/davecgh/go-spew v1.1.1 // indirect
144146
github.com/ebitengine/purego v0.8.2 // indirect
145147
github.com/ettle/strcase v0.2.0 // indirect

go.sum

+8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

jsonschema/golangci.next.jsonschema.json

+27
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,7 @@
368368
"gocyclo",
369369
"godot",
370370
"godox",
371+
"golines",
371372
"err113",
372373
"gofmt",
373374
"gofumpt",
@@ -1592,6 +1593,32 @@
15921593
}
15931594
}
15941595
},
1596+
"golines": {
1597+
"type": "object",
1598+
"additionalProperties": false,
1599+
"properties": {
1600+
"max-len": {
1601+
"type": "integer",
1602+
"default": 100
1603+
},
1604+
"tab-len": {
1605+
"type": "integer",
1606+
"default": 4
1607+
},
1608+
"shorten-comments": {
1609+
"type": "boolean",
1610+
"default": false
1611+
},
1612+
"reformat-tags": {
1613+
"type": "boolean",
1614+
"default": true
1615+
},
1616+
"chain-split-dots": {
1617+
"type": "boolean",
1618+
"default": true
1619+
}
1620+
}
1621+
},
15951622
"interfacebloat": {
15961623
"type": "object",
15971624
"additionalProperties": false,

pkg/config/formatters_settings.go

+15
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,20 @@ var defaultFormatterSettings = FormatterSettings{
88
Sections: []string{"standard", "default"},
99
SkipGenerated: true,
1010
},
11+
GoLines: GoLinesSettings{
12+
MaxLen: 100,
13+
TabLen: 4,
14+
ReformatTags: true,
15+
ChainSplitDots: true,
16+
},
1117
}
1218

1319
type FormatterSettings struct {
1420
Gci GciSettings `mapstructure:"gci"`
1521
GoFmt GoFmtSettings `mapstructure:"gofmt"`
1622
GoFumpt GoFumptSettings `mapstructure:"gofumpt"`
1723
GoImports GoImportsSettings `mapstructure:"goimports"`
24+
GoLines GoLinesSettings `mapstructure:"golines"`
1825
}
1926

2027
type GciSettings struct {
@@ -50,3 +57,11 @@ type GoFumptSettings struct {
5057
type GoImportsSettings struct {
5158
LocalPrefixes string `mapstructure:"local-prefixes"`
5259
}
60+
61+
type GoLinesSettings struct {
62+
MaxLen int `mapstructure:"max-len"`
63+
TabLen int `mapstructure:"tab-len"`
64+
ShortenComments bool `mapstructure:"shorten-comments"`
65+
ReformatTags bool `mapstructure:"reformat-tags"`
66+
ChainSplitDots bool `mapstructure:"chain-split-dots"`
67+
}

pkg/goformatters/golines/golines.go

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package golines
2+
3+
import (
4+
"github.com/golangci/golines"
5+
6+
"github.com/golangci/golangci-lint/pkg/config"
7+
)
8+
9+
const Name = "golines"
10+
11+
type Formatter struct {
12+
shortener *golines.Shortener
13+
}
14+
15+
func New(settings *config.GoLinesSettings) *Formatter {
16+
options := golines.ShortenerConfig{}
17+
18+
if settings != nil {
19+
options = golines.ShortenerConfig{
20+
MaxLen: settings.MaxLen,
21+
TabLen: settings.TabLen,
22+
KeepAnnotations: false, // golines debug (not usable inside golangci-lint)
23+
ShortenComments: settings.ShortenComments,
24+
ReformatTags: settings.ReformatTags,
25+
IgnoreGenerated: false, // handle globally
26+
DotFile: "", // golines debug (not usable inside golangci-lint)
27+
ChainSplitDots: settings.ChainSplitDots,
28+
BaseFormatterCmd: "go fmt", // fake cmd
29+
}
30+
}
31+
32+
return &Formatter{shortener: golines.NewShortener(options)}
33+
}
34+
35+
func (*Formatter) Name() string {
36+
return Name
37+
}
38+
39+
func (f *Formatter) Format(_ string, src []byte) ([]byte, error) {
40+
return f.shortener.Shorten(src)
41+
}

pkg/goformatters/meta_formatter.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/golangci/golangci-lint/pkg/goformatters/gofmt"
1212
"github.com/golangci/golangci-lint/pkg/goformatters/gofumpt"
1313
"github.com/golangci/golangci-lint/pkg/goformatters/goimports"
14+
"github.com/golangci/golangci-lint/pkg/goformatters/golines"
1415
"github.com/golangci/golangci-lint/pkg/logutils"
1516
)
1617

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

54+
// golines calls `format.Source()` internally so no need to format after it.
55+
if slices.Contains(cfg.Enable, golines.Name) {
56+
m.formatters = append(m.formatters, golines.New(&cfg.Settings.GoLines))
57+
}
58+
5359
return m, nil
5460
}
5561

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

8288
func IsFormatter(name string) bool {
83-
return slices.Contains([]string{gofmt.Name, gofumpt.Name, goimports.Name, gci.Name}, name)
89+
return slices.Contains([]string{gofmt.Name, gofumpt.Name, goimports.Name, gci.Name, golines.Name}, name)
8490
}

pkg/golinters/golines/golines.go

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package golines
2+
3+
import (
4+
"golang.org/x/tools/go/analysis"
5+
6+
"github.com/golangci/golangci-lint/pkg/config"
7+
"github.com/golangci/golangci-lint/pkg/goanalysis"
8+
"github.com/golangci/golangci-lint/pkg/goformatters"
9+
golinesbase "github.com/golangci/golangci-lint/pkg/goformatters/golines"
10+
"github.com/golangci/golangci-lint/pkg/golinters/internal"
11+
)
12+
13+
const linterName = "golines"
14+
15+
func New(settings *config.GoLinesSettings) *goanalysis.Linter {
16+
a := goformatters.NewAnalyzer(
17+
internal.LinterLogger.Child(linterName),
18+
"Checks if code is formatted, and fixes long lines",
19+
golinesbase.New(settings),
20+
)
21+
22+
return goanalysis.NewLinter(
23+
a.Name,
24+
a.Doc,
25+
[]*analysis.Analyzer{a},
26+
nil,
27+
).WithLoadMode(goanalysis.LoadModeSyntax)
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package golines
2+
3+
import (
4+
"testing"
5+
6+
"github.com/golangci/golangci-lint/test/testshared/integration"
7+
)
8+
9+
func TestFromTestdata(t *testing.T) {
10+
integration.RunTestdata(t)
11+
}
12+
13+
func TestFix(t *testing.T) {
14+
integration.RunFix(t)
15+
}
16+
17+
func TestFixPathPrefix(t *testing.T) {
18+
integration.RunFixPathPrefix(t)
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
//golangcitest:args -Egolines
2+
//golangcitest:expected_exitcode 0
3+
package testdata
4+
5+
import "fmt"
6+
7+
var (
8+
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")}
9+
)
10+
11+
type MyStruct struct {
12+
aLongProperty int `help:"This is a really long string for this property"`
13+
anotherLongProperty int `help:"This is a really long string for this property, part 2"`
14+
athirdLongProperty int `help:"This is a really long string for this property, part 3...."`
15+
}
16+
17+
type MyInterface interface {
18+
aReallyLongFunctionName(argument1 string, argument2 string, argument3 string, argument4 string, argument5 string, argument6 string) (string, error)
19+
}
20+
21+
// Something here
22+
23+
// Another comment
24+
// A third comment
25+
// 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!
26+
func longLine(aReallyLongName string, anotherLongName string, aThirdLongName string) (string, error) {
27+
argument1 := "argument1"
28+
argument2 := "argument2"
29+
argument3 := "argument3"
30+
argument4 := "argument4"
31+
32+
fmt.Printf("This is a really long string with a bunch of arguments: %s %s %s %s >>>>>>>>>>>>>>>>>>>>>>", argument1, argument2, argument3, argument4)
33+
fmt.Printf("This is a short statement: %d %d %d", 1, 2, 3)
34+
35+
z := argument1 + argument2 + fmt.Sprintf("This is a really long statement that should be broken up %s %s %s", argument1, argument2, argument3)
36+
37+
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))
38+
39+
fmt.Print("This is a function with a really long single argument. We want to see if it's properly split")
40+
41+
fmt.Println(z)
42+
43+
// 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?
44+
if argument4 == "5" {
45+
return "", fmt.Errorf("a very long query with ID %d failed. Check Query History in AWS UI", 12341251)
46+
}
47+
48+
go func() {
49+
fmt.Printf("This is a really long line inside of a go routine call. It should be split if at all possible.")
50+
}()
51+
52+
if "hello this is a big string" == "this is a small string" && "this is another big string" == "this is an even bigger string >>>" {
53+
fmt.Print("inside if statement")
54+
}
55+
56+
fmt.Println(map[string]string{"key1": "a very long value", "key2": "a very long value", "key3": "another very long value"})
57+
58+
return "", nil
59+
}
60+
61+
func shortFunc(a int, b int) error {
62+
c := make(chan int)
63+
64+
for {
65+
select {
66+
case <-c:
67+
switch a {
68+
case 1:
69+
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))
70+
case 2:
71+
}
72+
}
73+
74+
break
75+
}
76+
77+
if a > 5 {
78+
panic(fmt.Sprintf(">>>>>>>>>>>>>>>>>>> %s %s %s %s", "really long argument", "another really long argument", "a third really long arguement", abc[1:2]))
79+
}
80+
81+
return nil
82+
// This is an end decoration
83+
}

0 commit comments

Comments
 (0)