diff --git a/cmd/tagalign/tagalign.go b/cmd/tagalign/tagalign.go index 32b0c16..c663670 100644 --- a/cmd/tagalign/tagalign.go +++ b/cmd/tagalign/tagalign.go @@ -10,27 +10,32 @@ import ( ) func main() { - var autoSort bool - var fixedOrder string + var align bool + var sort bool + var order string // Just for declase - flag.BoolVar(&autoSort, "auto-sort", false, "enable auto sort tags") - flag.StringVar(&fixedOrder, "fixed-order", "", "specify the fixed order of tags, the other tags will be sorted by name") + flag.BoolVar(&align, "align", false, "Whether enable tags align. Default is true.") + flag.BoolVar(&sort, "sort", false, "Whether enable tags sort. Default is false.") + flag.StringVar(&order, "order", "", "Specify the order of tags, the other tags will be sorted by name.") // read from os.Args args := os.Args for i, arg := range args { - if arg == "-auto-sort" { - autoSort = true + if arg == "-align" { + align = true } - if arg == "-fixed-order" { - fixedOrder = args[i+1] + if arg == "-sort" { + sort = true + } + if arg == "-order" { + order = args[i+1] } } var options []tagalign.Option - if autoSort { - options = append(options, tagalign.WithAutoSort(strings.Split(fixedOrder, ",")...)) + if sort { + options = append(options, tagalign.WithAlign(align), tagalign.WithSort(strings.Split(order, ",")...)) } singlechecker.Main(tagalign.NewAnalyzer(options...)) diff --git a/options.go b/options.go index 53c25dc..4deaf8c 100644 --- a/options.go +++ b/options.go @@ -9,11 +9,20 @@ func WithMode(mode Mode) Option { } } -// WithAutoSort enable auto sort tags. -// Param fixedOrder specify the fixed order of tags, the other tags will be sorted by name. -func WithAutoSort(fixedOrder ...string) Option { +// WithSort enable tags sort. +// fixedOrder specify the order of tags, the other tags will be sorted by name. +// Sory is disabled by default. +func WithSort(fixedOrder ...string) Option { return func(h *Helper) { - h.autoSort = true + h.sort = true h.fixedTagOrder = fixedOrder } } + +// WithAlign configure whether enable tags align. +// Align is enabled by default. +func WithAlign(enabled bool) Option { + return func(h *Helper) { + h.align = enabled + } +} diff --git a/tagalign.go b/tagalign.go index f06131b..b4d72c9 100644 --- a/tagalign.go +++ b/tagalign.go @@ -5,6 +5,7 @@ import ( "go/ast" "go/token" "log" + "reflect" "sort" "strconv" "strings" @@ -35,25 +36,35 @@ func NewAnalyzer(options ...Option) *analysis.Analyzer { func Run(pass *analysis.Pass, options ...Option) []Issue { var issues []Issue for _, f := range pass.Files { - h := &Helper{mode: StandaloneMode} + h := &Helper{ + mode: StandaloneMode, + align: true, + } for _, opt := range options { opt(h) } + if h.align == false && h.sort == false { + // do nothing + return nil + } + ast.Inspect(f, func(n ast.Node) bool { h.find(pass, n) return true }) - h.align(pass) + h.Align(pass) issues = append(issues, h.issues...) } return issues } type Helper struct { - mode Mode - autoSort bool - fixedTagOrder []string // fixed tag order, the others will be sorted by name. + mode Mode + + 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. singleFields []*ast.Field consecutiveFieldsGroups [][]*ast.Field // fields in this group, must be consecutive in struct. @@ -131,13 +142,13 @@ func (w *Helper) find(pass *analysis.Pass, n ast.Node) { return } -func (w *Helper) align(pass *analysis.Pass) { - // sort and align fields groups +func (w *Helper) Align(pass *analysis.Pass) { + // process grouped fields for _, fields := range w.consecutiveFieldsGroups { offsets := make([]int, len(fields)) var maxTagNum int - var tagsGroup [][]*structtag.Tag + var tagsGroup, notSortedTagsGroup [][]*structtag.Tag for i, field := range fields { offsets[i] = pass.Fset.Position(field.Tag.Pos()).Column tag, err := strconv.Unquote(field.Tag.Value) @@ -152,13 +163,15 @@ func (w *Helper) align(pass *analysis.Pass) { maxTagNum = max(maxTagNum, tags.Len()) - if w.autoSort { + if w.sort { + notSortedTagsGroup = append(notSortedTagsGroup, tags.Tags()) sortBy(w.fixedTagOrder, tags) } tagsGroup = append(tagsGroup, tags.Tags()) } + // if w.align{ // record the max length of each column tag tagMaxLens := make([]int, maxTagNum) @@ -177,14 +190,29 @@ func (w *Helper) align(pass *analysis.Pass) { for i, field := range fields { tags := tagsGroup[i] - // new new builder - newTagBuilder := strings.Builder{} - for i, tag := range tags { - format := alignFormat(tagMaxLens[i] + 1) // with an extra space - newTagBuilder.WriteString(fmt.Sprintf(format, tag.String())) + var newTagStr string + 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())) + } + newTagStr = newTagBuilder.String() + } else { + // otherwise check if tags order changed + if w.sort && reflect.DeepEqual(notSortedTagsGroup[i], tags) { + // if tags order not changed, do nothing + continue + } + tagsStr := make([]string, len(tags)) + for i, tag := range tags { + tagsStr[i] = tag.String() + } + newTagStr = strings.Join(tagsStr, " ") } - unquoteTag := strings.TrimSpace(newTagBuilder.String()) + unquoteTag := strings.TrimSpace(newTagStr) newTagValue := fmt.Sprintf("`%s`", unquoteTag) if field.Tag.Value == newTagValue { // nothing changed @@ -228,7 +256,7 @@ func (w *Helper) align(pass *analysis.Pass) { } } - // sort single fields + // process single fields for _, field := range w.singleFields { tag, err := strconv.Unquote(field.Tag.Value) if err != nil { @@ -239,11 +267,16 @@ func (w *Helper) align(pass *analysis.Pass) { if err != nil { continue } - - if w.autoSort { + originalTags := append([]*structtag.Tag(nil), tags.Tags()...) + if w.sort { sortBy(w.fixedTagOrder, tags) } + if reflect.DeepEqual(originalTags, tags.Tags()) { + // if tags order not changed, do nothing + continue + } + newTagValue := fmt.Sprintf("`%s`", tags.String()) if field.Tag.Value == newTagValue { // nothing changed diff --git a/tagalign_test.go b/tagalign_test.go index d2e1920..72afbf4 100644 --- a/tagalign_test.go +++ b/tagalign_test.go @@ -19,7 +19,7 @@ func TestAnalyzer(t *testing.T) { } func TestAnalyzerWithOrder(t *testing.T) { // sort with fixed order - a := NewAnalyzer(WithAutoSort("json", "yaml", "xml")) + a := NewAnalyzer(WithSort("json", "yaml", "xml")) sort, err := filepath.Abs("testdata/sort") assert.NoError(t, err) analysistest.Run(t, sort, a) @@ -41,3 +41,11 @@ func Test_sortByFixedOrder(t *testing.T) { assert.Equal(t, "gorm", tags.Tags()[4].Key) assert.Equal(t, "zip", tags.Tags()[5].Key) } + +func Test_disableAlign(t *testing.T) { + // test disable align but enable sort + a := NewAnalyzer(WithAlign(false), WithSort("xml", "json", "yaml")) + sort, err := filepath.Abs("testdata/noalign") + assert.NoError(t, err) + analysistest.Run(t, sort, a) +} diff --git a/testdata/noalign/example.go b/testdata/noalign/example.go new file mode 100644 index 0000000..c00f167 --- /dev/null +++ b/testdata/noalign/example.go @@ -0,0 +1,12 @@ +package noalign + +type SortOnlyExample struct { + // not aligned but sorted, should not be reported + Foo int `xml:"baz" json:"foo,omitempty" yaml:"bar" binding:"required" gorm:"column:foo" validate:"required" zip:"foo" ` + Bar int `xml:"bar" json:"bar,omitempty" yaml:"foo" gorm:"column:bar" validate:"required" zip:"bar" ` + FooBar int `xml:"bar" json:"bar,omitempty" yaml:"foo" gorm:"column:bar" ` + // aligned but not sorted, should be reported + BarFoo int `xml:"bar" yaml:"foo" json:"bar,omitempty" gorm:"column:bar" validate:"required" zip:"bar"` // want `xml:"bar" json:"bar,omitempty" yaml:"foo" gorm:"column:bar" validate:"required" zip:"bar"` + // not aligned but sorted, should not be reported + FooBarFoo int `xml:"bar" json:"bar,omitempty" yaml:"foo" gorm:"column:bar" validate:"required" zip:"bar"` +}