Skip to content

Commit

Permalink
add tagalign linter (#3709)
Browse files Browse the repository at this point in the history
  • Loading branch information
4meepo committed Mar 26, 2023
1 parent 69f929b commit 134f2e0
Show file tree
Hide file tree
Showing 13 changed files with 218 additions and 0 deletions.
35 changes: 35 additions & 0 deletions .golangci.reference.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1739,6 +1739,39 @@ linters-settings:
# Default: ["200", "400", "404", "500"]
http-status-code-whitelist: [ "200", "400", "404", "500" ]

tagalign:
# Align and sort can be used together or separately.
#
# Whether enable align. If true, the struct tags will be aligned.
# eg:
# type FooBar struct {
# Bar string `json:"bar" validate:"required"`
# FooFoo int8 `json:"foo_foo" validate:"required"`
# }
# will be formatted to:
# type FooBar struct {
# Bar string `json:"bar" validate:"required"`
# FooFoo int8 `json:"foo_foo" validate:"required"`
# }
# Default: true.
align: false
# Whether enable tags sort.
# If true, the tags will be sorted by name in ascending order.
# eg: `xml:"bar" json:"bar" validate:"required"` -> `json:"bar" validate:"required" xml:"bar"`
# Default: true
sort: false
# Specify the order of tags, the other tags will be sorted by name.
# This option will be ignored if `sort` is false.
# Default: []
order:
- json
- yaml
- yml
- toml
- mapstructure
- binding
- validate

tagliatelle:
# Check the struct tag name case.
case:
Expand Down Expand Up @@ -2118,6 +2151,7 @@ linters:
- staticcheck
- structcheck
- stylecheck
- tagalign
- tagliatelle
- tenv
- testableexamples
Expand Down Expand Up @@ -2229,6 +2263,7 @@ linters:
- staticcheck
- structcheck
- stylecheck
- tagalign
- tagliatelle
- tenv
- testableexamples
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.19
require (
4d63.com/gocheckcompilerdirectives v1.2.1
4d63.com/gochecknoglobals v0.2.1
github.com/4meepo/tagalign v1.2.2
github.com/Abirdcfly/dupword v0.0.11
github.com/Antonboom/errname v0.1.9
github.com/Antonboom/nilnil v0.1.3
Expand Down
2 changes: 2 additions & 0 deletions go.sum

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

12 changes: 12 additions & 0 deletions pkg/config/linters_settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@ var defaultLintersSettings = LintersSettings{
Ignore: "",
Qualified: false,
},
TagAlign: TagAlignSettings{
Align: true,
Sort: true,
Order: nil,
},
Testpackage: TestpackageSettings{
SkipRegexp: `(export|internal)_test\.go`,
AllowPackages: []string{"main"},
Expand Down Expand Up @@ -203,6 +208,7 @@ type LintersSettings struct {
Staticcheck StaticCheckSettings
Structcheck StructCheckSettings
Stylecheck StaticCheckSettings
TagAlign TagAlignSettings
Tagliatelle TagliatelleSettings
Tenv TenvSettings
Testpackage TestpackageSettings
Expand Down Expand Up @@ -655,6 +661,12 @@ type StructCheckSettings struct {
CheckExportedFields bool `mapstructure:"exported-fields"`
}

type TagAlignSettings struct {
Align bool `mapstructure:"align"`
Sort bool `mapstructure:"sort"`
Order []string `mapstructure:"order"`
}

type TagliatelleSettings struct {
Case struct {
Rules map[string]string
Expand Down
70 changes: 70 additions & 0 deletions pkg/golinters/tagalign.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package golinters

import (
"sync"

"github.com/4meepo/tagalign"
"golang.org/x/tools/go/analysis"

"github.com/golangci/golangci-lint/pkg/config"
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
"github.com/golangci/golangci-lint/pkg/lint/linter"
"github.com/golangci/golangci-lint/pkg/result"
)

func NewTagAlign(settings *config.TagAlignSettings) *goanalysis.Linter {
var mu sync.Mutex
var resIssues []goanalysis.Issue

options := []tagalign.Option{tagalign.WithMode(tagalign.GolangciLintMode)}

if settings != nil {
options = append(options, tagalign.WithAlign(settings.Align))

if settings.Sort || len(settings.Order) > 0 {
options = append(options, tagalign.WithSort(settings.Order...))
}
}

analyzer := tagalign.NewAnalyzer(options...)
analyzer.Run = func(pass *analysis.Pass) (any, error) {
taIssues := tagalign.Run(pass, options...)

issues := make([]goanalysis.Issue, len(taIssues))
for i, issue := range taIssues {
report := &result.Issue{
FromLinter: analyzer.Name,
Pos: issue.Pos,
Text: issue.Message,
Replacement: &result.Replacement{
Inline: &result.InlineFix{
StartCol: issue.InlineFix.StartCol,
Length: issue.InlineFix.Length,
NewString: issue.InlineFix.NewString,
},
},
}

issues[i] = goanalysis.NewIssue(report, pass)
}

if len(issues) == 0 {
return nil, nil
}

mu.Lock()
resIssues = append(resIssues, issues...)
mu.Unlock()

return nil, nil
}

return goanalysis.NewLinter(
analyzer.Name,
analyzer.Doc,
[]*analysis.Analyzer{analyzer},
nil,
).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue {
return resIssues
}).WithLoadMode(goanalysis.LoadModeSyntax)
}
8 changes: 8 additions & 0 deletions pkg/lint/lintersdb/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config {
staticcheckCfg *config.StaticCheckSettings
structcheckCfg *config.StructCheckSettings
stylecheckCfg *config.StaticCheckSettings
tagalignCfg *config.TagAlignSettings
tagliatelleCfg *config.TagliatelleSettings
tenvCfg *config.TenvSettings
testpackageCfg *config.TestpackageSettings
Expand Down Expand Up @@ -244,6 +245,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config {
staticcheckCfg = &m.cfg.LintersSettings.Staticcheck
structcheckCfg = &m.cfg.LintersSettings.Structcheck
stylecheckCfg = &m.cfg.LintersSettings.Stylecheck
tagalignCfg = &m.cfg.LintersSettings.TagAlign
tagliatelleCfg = &m.cfg.LintersSettings.Tagliatelle
tenvCfg = &m.cfg.LintersSettings.Tenv
testpackageCfg = &m.cfg.LintersSettings.Testpackage
Expand Down Expand Up @@ -777,6 +779,12 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config {
WithPresets(linter.PresetStyle).
WithURL("https://github.com/dominikh/go-tools/tree/master/stylecheck"),

linter.NewConfig(golinters.NewTagAlign(tagalignCfg)).
WithSince("v1.53.0").
WithPresets(linter.PresetStyle, linter.PresetFormatting).
WithAutoFix().
WithURL("https://github.com/4meepo/tagalign"),

linter.NewConfig(golinters.NewTagliatelle(tagliatelleCfg)).
WithSince("v1.40.0").
WithPresets(linter.PresetStyle).
Expand Down
3 changes: 3 additions & 0 deletions test/testdata/configs/tagalign_align_only.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
linters-settings:
tagalign:
sort: false
7 changes: 7 additions & 0 deletions test/testdata/configs/tagalign_order_only.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
linters-settings:
tagalign:
align: false
order:
- "xml"
- "json"
- "yaml"
4 changes: 4 additions & 0 deletions test/testdata/configs/tagalign_sort_only.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
linters-settings:
tagalign:
align: false
sort: true
15 changes: 15 additions & 0 deletions test/testdata/tagalign.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//golangcitest:args -Etagalign
package testdata

import "time"

type TagAlignExampleAlignSort struct {
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"`
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"`
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"`
}

type TagAlignExampleAlignSort2 struct {
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"`
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"`
}
31 changes: 31 additions & 0 deletions test/testdata/tagalign_align_only.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//golangcitest:args -Etagalign
//golangcitest:config_path testdata/configs/tagalign_align_only.yml
package testdata

import "time"

type TagAlignExampleAlignOnlyKO struct {
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"`
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"`
FooFoo struct {
Foo int `json:"foo" yaml:"foo"` // want `json:"foo" yaml:"foo"`
Bar int `yaml:"bar" json:"bar"` // want `yaml:"bar" json:"bar"`
BarBar string `json:"barBar" yaml:"barBar"`
} `xml:"fooFoo" json:"fooFoo"`
NoTag struct{}
BarBar struct{} `json:"barBar,omitempty" gorm:"column:barBar" yaml:"barBar" xml:"barBar" zip:"barBar"`
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"`
}

type TagAlignExampleAlignOnlyOK struct {
Foo time.Time `gorm:"column:foo" json:"foo,omitempty" xml:"foo" yaml:"foo" zip:"foo"`
FooBar struct{} `gorm:"column:fooBar" zip:"fooBar" json:"fooBar,omitempty" xml:"fooBar" yaml:"fooBar"`
FooFoo struct {
Foo int `json:"foo" yaml:"foo"`
Bar int `yaml:"bar" json:"bar"`
BarBar string `json:"barBar" yaml:"barBar"`
} `xml:"fooFoo" json:"fooFoo"`
NoTag struct{}
BarBar struct{} `json:"barBar,omitempty" gorm:"column:barBar" yaml:"barBar" xml:"barBar" zip:"barBar"`
Boo struct{} `gorm:"column:boo" json:"boo,omitempty" xml:"boo" yaml:"boo" zip:"boo"`
}
15 changes: 15 additions & 0 deletions test/testdata/tagalign_order_only.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//golangcitest:args -Etagalign
//golangcitest:config_path testdata/configs/tagalign_order_only.yml
package testdata

import "time"

type TagAlignExampleOrderOnlyKO struct {
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"`
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"`
}

type TagAlignExampleOrderOnlyOK struct {
Foo time.Time `xml:"foo" json:"foo,omitempty" yaml:"foo" gorm:"column:foo" validate:"required" zip:"foo"`
FooBar struct{} `xml:"fooBar" json:"fooBar,omitempty" yaml:"fooBar" gorm:"column:fooBar" validate:"required" zip:"fooBar"`
}
15 changes: 15 additions & 0 deletions test/testdata/tagalign_sort_only.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//golangcitest:args -Etagalign
//golangcitest:config_path testdata/configs/tagalign_sort_only.yml
package testdata

import "time"

type TagAlignExampleSortOnlyKO struct {
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"`
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"`
}

type TagAlignExampleSortOnlyOK struct {
Foo time.Time `gorm:"column:foo" json:"foo,omitempty" validate:"required" xml:"foo" yaml:"foo" zip:"foo"`
FooBar struct{} `gorm:"column:fooBar" json:"fooBar,omitempty" validate:"required" xml:"fooBar" yaml:"fooBar" zip:"fooBar"`
}

0 comments on commit 134f2e0

Please sign in to comment.