Skip to content

Commit 134f2e0

Browse files
authored
add tagalign linter (#3709)
1 parent 69f929b commit 134f2e0

13 files changed

+218
-0
lines changed

.golangci.reference.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1739,6 +1739,39 @@ linters-settings:
17391739
# Default: ["200", "400", "404", "500"]
17401740
http-status-code-whitelist: [ "200", "400", "404", "500" ]
17411741

1742+
tagalign:
1743+
# Align and sort can be used together or separately.
1744+
#
1745+
# Whether enable align. If true, the struct tags will be aligned.
1746+
# eg:
1747+
# type FooBar struct {
1748+
# Bar string `json:"bar" validate:"required"`
1749+
# FooFoo int8 `json:"foo_foo" validate:"required"`
1750+
# }
1751+
# will be formatted to:
1752+
# type FooBar struct {
1753+
# Bar string `json:"bar" validate:"required"`
1754+
# FooFoo int8 `json:"foo_foo" validate:"required"`
1755+
# }
1756+
# Default: true.
1757+
align: false
1758+
# Whether enable tags sort.
1759+
# If true, the tags will be sorted by name in ascending order.
1760+
# eg: `xml:"bar" json:"bar" validate:"required"` -> `json:"bar" validate:"required" xml:"bar"`
1761+
# Default: true
1762+
sort: false
1763+
# Specify the order of tags, the other tags will be sorted by name.
1764+
# This option will be ignored if `sort` is false.
1765+
# Default: []
1766+
order:
1767+
- json
1768+
- yaml
1769+
- yml
1770+
- toml
1771+
- mapstructure
1772+
- binding
1773+
- validate
1774+
17421775
tagliatelle:
17431776
# Check the struct tag name case.
17441777
case:
@@ -2118,6 +2151,7 @@ linters:
21182151
- staticcheck
21192152
- structcheck
21202153
- stylecheck
2154+
- tagalign
21212155
- tagliatelle
21222156
- tenv
21232157
- testableexamples
@@ -2229,6 +2263,7 @@ linters:
22292263
- staticcheck
22302264
- structcheck
22312265
- stylecheck
2266+
- tagalign
22322267
- tagliatelle
22332268
- tenv
22342269
- testableexamples

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.19
55
require (
66
4d63.com/gocheckcompilerdirectives v1.2.1
77
4d63.com/gochecknoglobals v0.2.1
8+
github.com/4meepo/tagalign v1.2.2
89
github.com/Abirdcfly/dupword v0.0.11
910
github.com/Antonboom/errname v0.1.9
1011
github.com/Antonboom/nilnil v0.1.3

go.sum

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/config/linters_settings.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,11 @@ var defaultLintersSettings = LintersSettings{
110110
Ignore: "",
111111
Qualified: false,
112112
},
113+
TagAlign: TagAlignSettings{
114+
Align: true,
115+
Sort: true,
116+
Order: nil,
117+
},
113118
Testpackage: TestpackageSettings{
114119
SkipRegexp: `(export|internal)_test\.go`,
115120
AllowPackages: []string{"main"},
@@ -203,6 +208,7 @@ type LintersSettings struct {
203208
Staticcheck StaticCheckSettings
204209
Structcheck StructCheckSettings
205210
Stylecheck StaticCheckSettings
211+
TagAlign TagAlignSettings
206212
Tagliatelle TagliatelleSettings
207213
Tenv TenvSettings
208214
Testpackage TestpackageSettings
@@ -655,6 +661,12 @@ type StructCheckSettings struct {
655661
CheckExportedFields bool `mapstructure:"exported-fields"`
656662
}
657663

664+
type TagAlignSettings struct {
665+
Align bool `mapstructure:"align"`
666+
Sort bool `mapstructure:"sort"`
667+
Order []string `mapstructure:"order"`
668+
}
669+
658670
type TagliatelleSettings struct {
659671
Case struct {
660672
Rules map[string]string

pkg/golinters/tagalign.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package golinters
2+
3+
import (
4+
"sync"
5+
6+
"github.com/4meepo/tagalign"
7+
"golang.org/x/tools/go/analysis"
8+
9+
"github.com/golangci/golangci-lint/pkg/config"
10+
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
11+
"github.com/golangci/golangci-lint/pkg/lint/linter"
12+
"github.com/golangci/golangci-lint/pkg/result"
13+
)
14+
15+
func NewTagAlign(settings *config.TagAlignSettings) *goanalysis.Linter {
16+
var mu sync.Mutex
17+
var resIssues []goanalysis.Issue
18+
19+
options := []tagalign.Option{tagalign.WithMode(tagalign.GolangciLintMode)}
20+
21+
if settings != nil {
22+
options = append(options, tagalign.WithAlign(settings.Align))
23+
24+
if settings.Sort || len(settings.Order) > 0 {
25+
options = append(options, tagalign.WithSort(settings.Order...))
26+
}
27+
}
28+
29+
analyzer := tagalign.NewAnalyzer(options...)
30+
analyzer.Run = func(pass *analysis.Pass) (any, error) {
31+
taIssues := tagalign.Run(pass, options...)
32+
33+
issues := make([]goanalysis.Issue, len(taIssues))
34+
for i, issue := range taIssues {
35+
report := &result.Issue{
36+
FromLinter: analyzer.Name,
37+
Pos: issue.Pos,
38+
Text: issue.Message,
39+
Replacement: &result.Replacement{
40+
Inline: &result.InlineFix{
41+
StartCol: issue.InlineFix.StartCol,
42+
Length: issue.InlineFix.Length,
43+
NewString: issue.InlineFix.NewString,
44+
},
45+
},
46+
}
47+
48+
issues[i] = goanalysis.NewIssue(report, pass)
49+
}
50+
51+
if len(issues) == 0 {
52+
return nil, nil
53+
}
54+
55+
mu.Lock()
56+
resIssues = append(resIssues, issues...)
57+
mu.Unlock()
58+
59+
return nil, nil
60+
}
61+
62+
return goanalysis.NewLinter(
63+
analyzer.Name,
64+
analyzer.Doc,
65+
[]*analysis.Analyzer{analyzer},
66+
nil,
67+
).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue {
68+
return resIssues
69+
}).WithLoadMode(goanalysis.LoadModeSyntax)
70+
}

pkg/lint/lintersdb/manager.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config {
165165
staticcheckCfg *config.StaticCheckSettings
166166
structcheckCfg *config.StructCheckSettings
167167
stylecheckCfg *config.StaticCheckSettings
168+
tagalignCfg *config.TagAlignSettings
168169
tagliatelleCfg *config.TagliatelleSettings
169170
tenvCfg *config.TenvSettings
170171
testpackageCfg *config.TestpackageSettings
@@ -244,6 +245,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config {
244245
staticcheckCfg = &m.cfg.LintersSettings.Staticcheck
245246
structcheckCfg = &m.cfg.LintersSettings.Structcheck
246247
stylecheckCfg = &m.cfg.LintersSettings.Stylecheck
248+
tagalignCfg = &m.cfg.LintersSettings.TagAlign
247249
tagliatelleCfg = &m.cfg.LintersSettings.Tagliatelle
248250
tenvCfg = &m.cfg.LintersSettings.Tenv
249251
testpackageCfg = &m.cfg.LintersSettings.Testpackage
@@ -777,6 +779,12 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config {
777779
WithPresets(linter.PresetStyle).
778780
WithURL("https://github.com/dominikh/go-tools/tree/master/stylecheck"),
779781

782+
linter.NewConfig(golinters.NewTagAlign(tagalignCfg)).
783+
WithSince("v1.53.0").
784+
WithPresets(linter.PresetStyle, linter.PresetFormatting).
785+
WithAutoFix().
786+
WithURL("https://github.com/4meepo/tagalign"),
787+
780788
linter.NewConfig(golinters.NewTagliatelle(tagliatelleCfg)).
781789
WithSince("v1.40.0").
782790
WithPresets(linter.PresetStyle).
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
linters-settings:
2+
tagalign:
3+
sort: false
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
linters-settings:
2+
tagalign:
3+
align: false
4+
order:
5+
- "xml"
6+
- "json"
7+
- "yaml"
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
linters-settings:
2+
tagalign:
3+
align: false
4+
sort: true

test/testdata/tagalign.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//golangcitest:args -Etagalign
2+
package testdata
3+
4+
import "time"
5+
6+
type TagAlignExampleAlignSort struct {
7+
Foo time.Duration `json:"foo,omitempty" yaml:"foo" xml:"foo" binding:"required" gorm:"column:foo" zip:"foo" validate:"required"` // want `binding:"required" gorm:"column:foo" json:"foo,omitempty" validate:"required" xml:"foo" yaml:"foo" zip:"foo"`
8+
Bar int `validate:"required" yaml:"bar" xml:"bar" binding:"required" json:"bar,omitempty" gorm:"column:bar" zip:"bar" ` // want `binding:"required" gorm:"column:bar" json:"bar,omitempty" validate:"required" xml:"bar" yaml:"bar" zip:"bar"`
9+
FooBar int `gorm:"column:fooBar" validate:"required" xml:"fooBar" binding:"required" json:"fooBar,omitempty" zip:"fooBar" yaml:"fooBar"` // want `binding:"required" gorm:"column:fooBar" json:"fooBar,omitempty" validate:"required" xml:"fooBar" yaml:"fooBar" zip:"fooBar"`
10+
}
11+
12+
type TagAlignExampleAlignSort2 struct {
13+
Foo int ` xml:"foo" json:"foo,omitempty" yaml:"foo" zip:"foo" binding:"required" gorm:"column:foo" validate:"required"` // want `binding:"required" gorm:"column:foo" json:"foo,omitempty" validate:"required" xml:"foo" yaml:"foo" zip:"foo"`
14+
Bar int `validate:"required" gorm:"column:bar" yaml:"bar" xml:"bar" binding:"required" json:"bar" zip:"bar" ` // want `binding:"required" gorm:"column:bar" json:"bar" validate:"required" xml:"bar" yaml:"bar" zip:"bar"`
15+
}

test/testdata/tagalign_align_only.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//golangcitest:args -Etagalign
2+
//golangcitest:config_path testdata/configs/tagalign_align_only.yml
3+
package testdata
4+
5+
import "time"
6+
7+
type TagAlignExampleAlignOnlyKO struct {
8+
Foo time.Time `gorm:"column:foo" json:"foo,omitempty" xml:"foo" yaml:"foo" zip:"foo"` // want `gorm:"column:foo" json:"foo,omitempty" xml:"foo" yaml:"foo" zip:"foo"`
9+
FooBar struct{} `gorm:"column:fooBar" zip:"fooBar" json:"fooBar,omitempty" xml:"fooBar" yaml:"fooBar"` // want `gorm:"column:fooBar" zip:"fooBar" json:"fooBar,omitempty" xml:"fooBar" yaml:"fooBar"`
10+
FooFoo struct {
11+
Foo int `json:"foo" yaml:"foo"` // want `json:"foo" yaml:"foo"`
12+
Bar int `yaml:"bar" json:"bar"` // want `yaml:"bar" json:"bar"`
13+
BarBar string `json:"barBar" yaml:"barBar"`
14+
} `xml:"fooFoo" json:"fooFoo"`
15+
NoTag struct{}
16+
BarBar struct{} `json:"barBar,omitempty" gorm:"column:barBar" yaml:"barBar" xml:"barBar" zip:"barBar"`
17+
Boo struct{} `gorm:"column:boo" json:"boo,omitempty" xml:"boo" yaml:"boo" zip:"boo"` // want `gorm:"column:boo" json:"boo,omitempty" xml:"boo" yaml:"boo" zip:"boo"`
18+
}
19+
20+
type TagAlignExampleAlignOnlyOK struct {
21+
Foo time.Time `gorm:"column:foo" json:"foo,omitempty" xml:"foo" yaml:"foo" zip:"foo"`
22+
FooBar struct{} `gorm:"column:fooBar" zip:"fooBar" json:"fooBar,omitempty" xml:"fooBar" yaml:"fooBar"`
23+
FooFoo struct {
24+
Foo int `json:"foo" yaml:"foo"`
25+
Bar int `yaml:"bar" json:"bar"`
26+
BarBar string `json:"barBar" yaml:"barBar"`
27+
} `xml:"fooFoo" json:"fooFoo"`
28+
NoTag struct{}
29+
BarBar struct{} `json:"barBar,omitempty" gorm:"column:barBar" yaml:"barBar" xml:"barBar" zip:"barBar"`
30+
Boo struct{} `gorm:"column:boo" json:"boo,omitempty" xml:"boo" yaml:"boo" zip:"boo"`
31+
}

test/testdata/tagalign_order_only.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//golangcitest:args -Etagalign
2+
//golangcitest:config_path testdata/configs/tagalign_order_only.yml
3+
package testdata
4+
5+
import "time"
6+
7+
type TagAlignExampleOrderOnlyKO struct {
8+
Foo time.Time `xml:"foo" json:"foo,omitempty" yaml:"foo" zip:"foo" gorm:"column:foo" validate:"required"` // want `xml:"foo" json:"foo,omitempty" yaml:"foo" gorm:"column:foo" validate:"required" zip:"foo"`
9+
FooBar struct{} `gorm:"column:fooBar" validate:"required" zip:"fooBar" xml:"fooBar" json:"fooBar,omitempty" yaml:"fooBar"` // want `xml:"fooBar" json:"fooBar,omitempty" yaml:"fooBar" gorm:"column:fooBar" validate:"required" zip:"fooBar"`
10+
}
11+
12+
type TagAlignExampleOrderOnlyOK struct {
13+
Foo time.Time `xml:"foo" json:"foo,omitempty" yaml:"foo" gorm:"column:foo" validate:"required" zip:"foo"`
14+
FooBar struct{} `xml:"fooBar" json:"fooBar,omitempty" yaml:"fooBar" gorm:"column:fooBar" validate:"required" zip:"fooBar"`
15+
}

test/testdata/tagalign_sort_only.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//golangcitest:args -Etagalign
2+
//golangcitest:config_path testdata/configs/tagalign_sort_only.yml
3+
package testdata
4+
5+
import "time"
6+
7+
type TagAlignExampleSortOnlyKO struct {
8+
Foo time.Time `xml:"foo" json:"foo,omitempty" yaml:"foo" gorm:"column:foo" validate:"required" zip:"foo"` // want `gorm:"column:foo" json:"foo,omitempty" validate:"required" xml:"foo" yaml:"foo" zip:"foo"`
9+
FooBar struct{} `gorm:"column:fooBar" validate:"required" zip:"fooBar" xml:"fooBar" json:"fooBar,omitempty" yaml:"fooBar"` // want `gorm:"column:fooBar" json:"fooBar,omitempty" validate:"required" xml:"fooBar" yaml:"fooBar" zip:"fooBar"`
10+
}
11+
12+
type TagAlignExampleSortOnlyOK struct {
13+
Foo time.Time `gorm:"column:foo" json:"foo,omitempty" validate:"required" xml:"foo" yaml:"foo" zip:"foo"`
14+
FooBar struct{} `gorm:"column:fooBar" json:"fooBar,omitempty" validate:"required" xml:"fooBar" yaml:"fooBar" zip:"fooBar"`
15+
}

0 commit comments

Comments
 (0)