Skip to content

Commit

Permalink
strict style
Browse files Browse the repository at this point in the history
  • Loading branch information
4meepo committed Jul 26, 2023
1 parent da67eb2 commit cc31979
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 15 deletions.
3 changes: 1 addition & 2 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ linters-settings:
- name: receiver-naming
- name: redefines-builtin-id
- name: string-of-int
- name: struct-tag
- name: superfluous-else
- name: time-naming
- name: unconditional-recursion
Expand Down Expand Up @@ -81,7 +80,7 @@ linters:
- goprintffuncname
- gosec
- gosimple
- govet
# - govet
- importas
- ineffassign
- makezero
Expand Down
11 changes: 11 additions & 0 deletions cmd/tagalign/tagalign.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ func main() {
var noalign bool
var sort bool
var order string
var strict bool

// just for declaration.
flag.BoolVar(&noalign, "noalign", false, "Whether disable tags align. Default is false.")
flag.BoolVar(&sort, "sort", false, "Whether enable tags sort. Default is false.")
flag.BoolVar(&strict, "strict", false, "Whether enable strict style. Default is false. Note: strict must be used with align and sort together.")
flag.StringVar(&order, "order", "", "Specify the order of tags, the other tags will be sorted by name.")

// read from os.Args
Expand All @@ -28,6 +30,9 @@ func main() {
if arg == "-sort" {
sort = true
}
if arg == "-strict" {
strict = true
}
if arg == "-order" {
order = args[i+1]
}
Expand All @@ -44,6 +49,12 @@ func main() {
}
options = append(options, tagalign.WithSort(orders...))
}
if strict {
if noalign || !sort {
panic("`-strict` flag must be used with `-align` and `-sort` together")
}
options = append(options, tagalign.WithStrictStyle())
}

singlechecker.Main(tagalign.NewAnalyzer(options...))
}
7 changes: 7 additions & 0 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,10 @@ func WithAlign(enabled bool) Option {
h.align = enabled
}
}

// WithStyle specify the style of tagalign.
func WithStrictStyle() Option {
return func(h *Helper) {
h.style = StrictStyle
}
}
109 changes: 96 additions & 13 deletions tagalign.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ const (
GolangciLintMode
)

type Style int

const (
DefaultStyle Style = iota
StrictStyle
)

func NewAnalyzer(options ...Option) *analysis.Analyzer {
return &analysis.Analyzer{
Name: "tagalign",
Expand All @@ -38,6 +45,7 @@ func Run(pass *analysis.Pass, options ...Option) []Issue {
for _, f := range pass.Files {
h := &Helper{
mode: StandaloneMode,
style: DefaultStyle,
align: true,
}
for _, opt := range options {
Expand All @@ -62,6 +70,8 @@ func Run(pass *analysis.Pass, options ...Option) []Issue {
type Helper struct {
mode Mode

style Style

align bool // whether enable tags align.
sort bool // whether enable tags sort.
fixedTagOrder []string // the order of tags, the other tags will be sorted by name.
Expand Down Expand Up @@ -182,6 +192,17 @@ func (w *Helper) Process(pass *analysis.Pass) { //nolint:gocognit

var maxTagNum int
var tagsGroup, notSortedTagsGroup [][]*structtag.Tag

var uniqueKeys []string
addKey := func(k string) {
for _, key := range uniqueKeys {
if key == k {
return
}
}
uniqueKeys = append(uniqueKeys, k)
}

for i, field := range fields {
offsets[i] = pass.Fset.Position(field.Tag.Pos()).Column
tag, err := strconv.Unquote(field.Tag.Value)
Expand All @@ -204,24 +225,45 @@ func (w *Helper) Process(pass *analysis.Pass) { //nolint:gocognit
notSortedTagsGroup = append(notSortedTagsGroup, cp)
sortBy(w.fixedTagOrder, tags)
}

for _, t := range tags.Tags() {
addKey(t.Key)
}
tagsGroup = append(tagsGroup, tags.Tags())
}

// if w.align{
// record the max length of each column tag
tagMaxLens := make([]int, maxTagNum)
if w.sort && StrictStyle == w.style {
sortAllKeys(w.fixedTagOrder, uniqueKeys)
maxTagNum = len(uniqueKeys)
}

// record the max length of each column tag
type tagLen struct {
Key string // present only when sort enabled
Len int
}
tagMaxLens := make([]tagLen, maxTagNum)
for j := 0; j < maxTagNum; j++ {
var maxLength int
var key string
for i := 0; i < len(tagsGroup); i++ {
if len(tagsGroup[i]) <= j {
// in case of index out of range
continue
if w.style == StrictStyle {
key = uniqueKeys[j]
// search by key
for _, tag := range tagsGroup[i] {
if tag.Key == key {
maxLength = max(maxLength, len(tag.String()))
break
}
}
} else {
if len(tagsGroup[i]) <= j {
// in case of index out of range
continue
}
maxLength = max(maxLength, len(tagsGroup[i][j].String()))
}
maxLength = max(maxLength, len(tagsGroup[i][j].String()))
}
tagMaxLens[j] = maxLength
tagMaxLens[j] = tagLen{key, maxLength}
}

for i, field := range fields {
Expand All @@ -231,9 +273,28 @@ func (w *Helper) Process(pass *analysis.Pass) { //nolint:gocognit
if w.align {
// if align enabled, align tags.
newTagBuilder := strings.Builder{}
for i, tag := range tags {
format := alignFormat(tagMaxLens[i] + 1) // with an extra space
newTagBuilder.WriteString(fmt.Sprintf(format, tag.String()))
for i, n := 0, 0; i < len(tags) && n < len(tagMaxLens); {
tag := tags[i]
var format string
if w.style == StrictStyle {
if tagMaxLens[n].Key == tag.Key {
// match
format = alignFormat(tagMaxLens[n].Len + 1) // with an extra space
newTagBuilder.WriteString(fmt.Sprintf(format, tag.String()))
i++
n++
} else {
// tag missing
format = alignFormat(tagMaxLens[n].Len + 1)
newTagBuilder.WriteString(fmt.Sprintf(format, ""))
n++
}
} else {
format = alignFormat(tagMaxLens[n].Len + 1) // with an extra space
newTagBuilder.WriteString(fmt.Sprintf(format, tag.String()))
i++
n++
}
}
newTagStr = newTagBuilder.String()
} else {
Expand All @@ -249,7 +310,8 @@ func (w *Helper) Process(pass *analysis.Pass) { //nolint:gocognit
newTagStr = strings.Join(tagsStr, " ")
}

unquoteTag := strings.TrimSpace(newTagStr)
unquoteTag := strings.TrimRight(newTagStr, " ")
// unquoteTag := newTagStr
newTagValue := fmt.Sprintf("`%s`", unquoteTag)
if field.Tag.Value == newTagValue {
// nothing changed
Expand Down Expand Up @@ -329,6 +391,27 @@ func sortBy(fixedOrder []string, tags *structtag.Tags) {
})
}

func sortAllKeys(fixedOrder []string, keys []string) {
sort.Slice(keys, func(i, j int) bool {
oi := findIndex(fixedOrder, keys[i])
oj := findIndex(fixedOrder, keys[j])

if oi == -1 && oj == -1 {
return keys[i] < keys[j]
}

if oi == -1 {
return false
}

if oj == -1 {
return true
}

return oi < oj
})
}

func findIndex(s []string, e string) int {
for i, a := range s {
if a == e {
Expand Down
8 changes: 8 additions & 0 deletions tagalign_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,11 @@ func Test_sortBy(t *testing.T) {
assert.Equal(t, "gorm", tags.Tags()[4].Key)
assert.Equal(t, "zip", tags.Tags()[5].Key)
}

func Test_strictStyle(t *testing.T) {
// align and sort with fixed order
a := NewAnalyzer(WithSort("json", "yaml", "xml"), WithStrictStyle())
sort, err := filepath.Abs("testdata/strict")
assert.NoError(t, err)
analysistest.Run(t, sort, a)
}
18 changes: 18 additions & 0 deletions testdata/strict/example.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package strict

type AlignAndSortWithOrderExample struct {
Foo int `json:"foo,omitempty" yaml:"bar" xml:"baz" binding:"required" gorm:"column:foo" zip:"foo" validate:"required"` // want `tag is not aligned, should be: json:"foo,omitempty" yaml:"bar" xml:"baz" binding:"required" gorm:"column:foo" validate:"required" zip:"foo"`
Bar int `validate:"required" yaml:"foo" xml:"bar" binding:"required" json:"bar,omitempty" gorm:"column:bar" zip:"bar" ` // want `tag is not aligned, should be: json:"bar,omitempty" yaml:"foo" xml:"bar" binding:"required" gorm:"column:bar" validate:"required" zip:"bar"`
FooBar int `gorm:"column:bar" validate:"required" xml:"bar" binding:"required" json:"bar,omitempty" zip:"bar" yaml:"foo"` // want `tag is not aligned, should be: json:"bar,omitempty" yaml:"foo" xml:"bar" binding:"required" gorm:"column:bar" validate:"required" zip:"bar"`
}

type AlignAndSortWithOrderExample2 struct {
Foo int ` xml:"baz" yaml:"bar" zip:"foo" binding:"required" gorm:"column:foo" validate:"required"` // want `tag is not aligned, should be: yaml:"bar" xml:"baz" binding:"required" gorm:"column:foo" validate:"required" zip:"foo"`
Bar int `validate:"required" gorm:"column:bar" yaml:"foo" xml:"bar" binding:"required" json:"bar,omitempty" ` // want `tag is not aligned, should be: json:"bar,omitempty" yaml:"foo" xml:"bar" binding:"required" gorm:"column:bar" validate:"required"`
}

type AlignAndSortWithOrderExample3 struct {
Foo int ` zip:"foo" gorm:"column:foo"` // want `tag is not aligned, should be: gorm:"column:foo" zip:"foo"`
Bar int `binding:"required" gorm:"column:bar" validate:"required" xml:"barxxxxxxxxxxxx" yaml:"foo" zip:"bar" json:"bar,omitempty" ` // want `tag is not aligned, should be: json:"bar,omitempty" yaml:"foo" xml:"barxxxxxxxxxxxx" binding:"required" gorm:"column:bar" validate:"required" zip:"bar"`
FooBar int `binding:"required" gorm:"column:bar" json:"bar,omitempty" validate:"required" yaml:"foo" zip:"bar"` // want `tag is not aligned, should be: json:"bar,omitempty" yaml:"foo" binding:"required" gorm:"column:bar" validate:"required" zip:"bar"`
}

0 comments on commit cc31979

Please sign in to comment.