From 6e4e3e813aad77401d7cd6c7f724c29eea51807f Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Sun, 22 Dec 2024 19:33:00 +0100 Subject: [PATCH 1/7] chore: remove golangci-lint mode --- .gitignore | 1 + go.mod | 2 +- options.go | 7 ---- tagalign.go | 105 ++++++++++++---------------------------------------- 4 files changed, 26 insertions(+), 89 deletions(-) diff --git a/.gitignore b/.gitignore index e37bb52..1c6218e 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ *.test .vscode +.idea/ # Output of the go coverage tool, specifically when used with LiteIDE *.out diff --git a/go.mod b/go.mod index 7abad10..86c035a 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/4meepo/tagalign -go 1.19 +go 1.21.0 require ( github.com/fatih/structtag v1.2.0 diff --git a/options.go b/options.go index ddec98d..2a78592 100644 --- a/options.go +++ b/options.go @@ -2,13 +2,6 @@ package tagalign type Option func(*Helper) -// WithMode specify the mode of tagalign. -func WithMode(mode Mode) Option { - return func(h *Helper) { - h.mode = mode - } -} - // WithSort enable tags sort. // fixedOrder specify the order of tags, the other tags will be sorted by name. // Sory is disabled by default. diff --git a/tagalign.go b/tagalign.go index 4734b56..0e17740 100644 --- a/tagalign.go +++ b/tagalign.go @@ -3,8 +3,6 @@ package tagalign import ( "fmt" "go/ast" - "go/token" - "log" "reflect" "sort" "strconv" @@ -15,13 +13,6 @@ import ( "golang.org/x/tools/go/analysis" ) -type Mode int - -const ( - StandaloneMode Mode = iota - GolangciLintMode -) - type Style int const ( @@ -44,11 +35,9 @@ func NewAnalyzer(options ...Option) *analysis.Analyzer { } } -func Run(pass *analysis.Pass, options ...Option) []Issue { - var issues []Issue +func Run(pass *analysis.Pass, options ...Option) { for _, f := range pass.Files { h := &Helper{ - mode: StandaloneMode, style: DefaultStyle, align: true, } @@ -63,22 +52,19 @@ func Run(pass *analysis.Pass, options ...Option) []Issue { if !h.align && !h.sort { // do nothing - return nil + return } ast.Inspect(f, func(n ast.Node) bool { h.find(pass, n) return true }) + h.Process(pass) - issues = append(issues, h.issues...) } - return issues } type Helper struct { - mode Mode - style Style align bool // whether enable tags align. @@ -87,19 +73,6 @@ type Helper struct { singleFields []*ast.Field consecutiveFieldsGroups [][]*ast.Field // fields in this group, must be consecutive in struct. - issues []Issue -} - -// Issue is used to integrate with golangci-lint's inline auto fix. -type Issue struct { - Pos token.Position - Message string - InlineFix InlineFix -} -type InlineFix struct { - StartCol int // zero-based - Length int - NewString string } func (w *Helper) find(pass *analysis.Pass, n ast.Node) { @@ -159,39 +132,24 @@ func (w *Helper) find(pass *analysis.Pass, n ast.Node) { split() } -func (w *Helper) report(pass *analysis.Pass, field *ast.Field, startCol int, msg, replaceStr string) { - if w.mode == GolangciLintMode { - iss := Issue{ - Pos: pass.Fset.Position(field.Tag.Pos()), - Message: msg, - InlineFix: InlineFix{ - StartCol: startCol, - Length: len(field.Tag.Value), - NewString: replaceStr, - }, - } - w.issues = append(w.issues, iss) - } - - if w.mode == StandaloneMode { - pass.Report(analysis.Diagnostic{ - Pos: field.Tag.Pos(), - End: field.Tag.End(), - Message: msg, - SuggestedFixes: []analysis.SuggestedFix{ - { - Message: msg, - TextEdits: []analysis.TextEdit{ - { - Pos: field.Tag.Pos(), - End: field.Tag.End(), - NewText: []byte(replaceStr), - }, +func (w *Helper) report(pass *analysis.Pass, field *ast.Field, msg, replaceStr string) { + pass.Report(analysis.Diagnostic{ + Pos: field.Tag.Pos(), + End: field.Tag.End(), + Message: msg, + SuggestedFixes: []analysis.SuggestedFix{ + { + Message: msg, + TextEdits: []analysis.TextEdit{ + { + Pos: field.Tag.Pos(), + End: field.Tag.End(), + NewText: []byte(replaceStr), }, }, }, - }) - } + }, + }) } func (w *Helper) Process(pass *analysis.Pass) { //nolint:gocognit @@ -220,7 +178,7 @@ func (w *Helper) Process(pass *analysis.Pass) { //nolint:gocognit tag, err := strconv.Unquote(field.Tag.Value) if err != nil { // if tag value is not a valid string, report it directly - w.report(pass, field, column, errTagValueSyntax, field.Tag.Value) + w.report(pass, field, errTagValueSyntax, field.Tag.Value) fields = removeField(fields, i) continue } @@ -228,7 +186,7 @@ func (w *Helper) Process(pass *analysis.Pass) { //nolint:gocognit tags, err := structtag.Parse(tag) if err != nil { // if tag value is not a valid struct tag, report it directly - w.report(pass, field, column, err.Error(), field.Tag.Value) + w.report(pass, field, err.Error(), field.Tag.Value) fields = removeField(fields, i) continue } @@ -340,22 +298,21 @@ func (w *Helper) Process(pass *analysis.Pass) { //nolint:gocognit msg := "tag is not aligned, should be: " + unquoteTag - w.report(pass, field, offsets[i], msg, newTagValue) + w.report(pass, field, msg, newTagValue) } } // process single fields for _, field := range w.singleFields { - column := pass.Fset.Position(field.Tag.Pos()).Column - 1 tag, err := strconv.Unquote(field.Tag.Value) if err != nil { - w.report(pass, field, column, errTagValueSyntax, field.Tag.Value) + w.report(pass, field, errTagValueSyntax, field.Tag.Value) continue } tags, err := structtag.Parse(tag) if err != nil { - w.report(pass, field, column, err.Error(), field.Tag.Value) + w.report(pass, field, err.Error(), field.Tag.Value) continue } originalTags := append([]*structtag.Tag(nil), tags.Tags()...) @@ -371,17 +328,10 @@ func (w *Helper) Process(pass *analysis.Pass) { //nolint:gocognit msg := "tag is not aligned , should be: " + tags.String() - w.report(pass, field, column, msg, newTagValue) + w.report(pass, field, msg, newTagValue) } } -// Issues returns all issues found by the analyzer. -// It is used to integrate with golangci-lint. -func (w *Helper) Issues() []Issue { - log.Println("tagalign 's Issues() should only be called in golangci-lint mode") - return w.issues -} - // sortBy sorts tags by fixed order. // If a tag is not in the fixed order, it will be sorted by name. func sortBy(fixedOrder []string, tags *structtag.Tags) { @@ -443,13 +393,6 @@ func alignFormat(length int) string { return "%" + fmt.Sprintf("-%ds", length) } -func max(a, b int) int { - if a > b { - return a - } - return b -} - func removeField(fields []*ast.Field, index int) []*ast.Field { if index < 0 || index >= len(fields) { return fields From cfe6a7ce158df0f6554e102f943587b0c19de463 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Sun, 22 Dec 2024 19:36:56 +0100 Subject: [PATCH 2/7] chore: update workflows --- .github/workflows/main.yaml | 8 ++++---- .github/workflows/release.yml | 7 +++---- .goreleaser.yml | 4 ++-- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 95f542f..a096302 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -6,13 +6,13 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: fetch-depth: 2 - - uses: actions/setup-go@v2 + - uses: actions/setup-go@v5 with: - go-version: '1.19' + go-version: stable - name: Run coverage run: go test -race -coverprofile=coverage.txt -covermode=atomic - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 \ No newline at end of file + uses: codecov/codecov-action@v3 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7d5d7f5..1aaf38a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,16 +15,15 @@ jobs: goreleaser: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - - run: git fetch --force --tags - - uses: actions/setup-go@v4 + - uses: actions/setup-go@v5 with: go-version: stable # More assembly might be required: Docker logins, GPG, etc. It all depends # on your needs. - - uses: goreleaser/goreleaser-action@v4 + - uses: goreleaser/goreleaser-action@v6 with: # either 'goreleaser' (default) or 'goreleaser-pro': distribution: goreleaser diff --git a/.goreleaser.yml b/.goreleaser.yml index e7b6f68..37dfec7 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,4 +1,4 @@ ---- +version: 2 project_name: tagalign release: @@ -29,4 +29,4 @@ builds: goarch: 386 - goos: freebsd goarch: arm64 - main: ./cmd/tagalign/ \ No newline at end of file + main: ./cmd/tagalign/ From 816d426181db9b2db3079f58540d41723356395e Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Sun, 22 Dec 2024 19:38:03 +0100 Subject: [PATCH 3/7] chore: fix linter --- tagalign.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tagalign.go b/tagalign.go index 0e17740..294044c 100644 --- a/tagalign.go +++ b/tagalign.go @@ -152,7 +152,8 @@ func (w *Helper) report(pass *analysis.Pass, field *ast.Field, msg, replaceStr s }) } -func (w *Helper) Process(pass *analysis.Pass) { //nolint:gocognit +//nolint:gocognit,gocyclo,nestif +func (w *Helper) Process(pass *analysis.Pass) { // process grouped fields for _, fields := range w.consecutiveFieldsGroups { offsets := make([]int, len(fields)) From 6e7cd01e8b119f83b3dca49d08ac33703849824b Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Sun, 22 Dec 2024 19:40:59 +0100 Subject: [PATCH 4/7] chore: simplify sorting --- tagalign.go | 65 ++++++++++++++++-------------------------------- tagalign_test.go | 6 ++--- 2 files changed, 24 insertions(+), 47 deletions(-) diff --git a/tagalign.go b/tagalign.go index 294044c..8896a14 100644 --- a/tagalign.go +++ b/tagalign.go @@ -1,10 +1,11 @@ package tagalign import ( + "cmp" "fmt" "go/ast" "reflect" - "sort" + "slices" "strconv" "strings" @@ -200,7 +201,7 @@ func (w *Helper) Process(pass *analysis.Pass) { cp[i] = tag } notSortedTagsGroup = append(notSortedTagsGroup, cp) - sortBy(w.fixedTagOrder, tags) + sortTags(w.fixedTagOrder, tags) } for _, t := range tags.Tags() { addKey(t.Key) @@ -211,7 +212,7 @@ func (w *Helper) Process(pass *analysis.Pass) { } if w.sort && StrictStyle == w.style { - sortAllKeys(w.fixedTagOrder, uniqueKeys) + sortKeys(w.fixedTagOrder, uniqueKeys) maxTagNum = len(uniqueKeys) } @@ -318,7 +319,7 @@ func (w *Helper) Process(pass *analysis.Pass) { } originalTags := append([]*structtag.Tag(nil), tags.Tags()...) if w.sort { - sortBy(w.fixedTagOrder, tags) + sortTags(w.fixedTagOrder, tags) } newTagValue := fmt.Sprintf("`%s`", tags.String()) @@ -333,61 +334,37 @@ func (w *Helper) Process(pass *analysis.Pass) { } } -// sortBy sorts tags by fixed order. +// sortTags sorts tags by fixed order. // If a tag is not in the fixed order, it will be sorted by name. -func sortBy(fixedOrder []string, tags *structtag.Tags) { - // sort by fixed order - sort.Slice(tags.Tags(), func(i, j int) bool { - ti := tags.Tags()[i] - tj := tags.Tags()[j] - - oi := findIndex(fixedOrder, ti.Key) - oj := findIndex(fixedOrder, tj.Key) - - if oi == -1 && oj == -1 { - return ti.Key < tj.Key - } - - if oi == -1 { - return false - } - - if oj == -1 { - return true - } - - return oi < oj +func sortTags(fixedOrder []string, tags *structtag.Tags) { + slices.SortFunc(tags.Tags(), func(a, b *structtag.Tag) int { + return compareByFixedOrder(fixedOrder)(a.Key, b.Key) }) } -func sortAllKeys(fixedOrder []string, keys []string) { - sort.Slice(keys, func(i, j int) bool { - oi := findIndex(fixedOrder, keys[i]) - oj := findIndex(fixedOrder, keys[j]) +func sortKeys(fixedOrder []string, keys []string) { + slices.SortFunc(keys, compareByFixedOrder(fixedOrder)) +} + +func compareByFixedOrder(fixedOrder []string) func(a, b string) int { + return func(a, b string) int { + oi := slices.Index(fixedOrder, a) + oj := slices.Index(fixedOrder, b) if oi == -1 && oj == -1 { - return keys[i] < keys[j] + return strings.Compare(a, b) } if oi == -1 { - return false + return 1 } if oj == -1 { - return true + return -1 } - return oi < oj - }) -} - -func findIndex(s []string, e string) int { - for i, a := range s { - if a == e { - return i - } + return cmp.Compare(oi, oj) } - return -1 } func alignFormat(length int) string { diff --git a/tagalign_test.go b/tagalign_test.go index 20d58d4..2060dc1 100644 --- a/tagalign_test.go +++ b/tagalign_test.go @@ -40,16 +40,16 @@ func Test_alignAndSortWithOrder(t *testing.T) { analysistest.Run(t, sort, a) } -func TestSprintf(t *testing.T) { +func Test_alignFormat(t *testing.T) { format := alignFormat(20) assert.Equal(t, "%-20s", format) } -func Test_sortBy(t *testing.T) { +func Test_sortTags(t *testing.T) { tags, err := structtag.Parse(`zip:"foo" json:"foo,omitempty" yaml:"bar" binding:"required" xml:"baz" gorm:"column:foo"`) assert.NoError(t, err) - sortBy([]string{"json", "yaml", "xml"}, tags) + sortTags([]string{"json", "yaml", "xml"}, tags) assert.Equal(t, "json", tags.Tags()[0].Key) assert.Equal(t, "yaml", tags.Tags()[1].Key) assert.Equal(t, "xml", tags.Tags()[2].Key) From 196ff4eda6cce74f885307ae056df69919c5054b Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Sun, 22 Dec 2024 19:53:37 +0100 Subject: [PATCH 5/7] tests: rewrite analyzer tests --- tagalign_test.go | 97 +++++++++---------- testdata/{align => src/align_only}/example.go | 0 testdata/{ => src}/alignsortorder/example.go | 0 testdata/{ => src}/bad_syntax_tag/example.go | 0 testdata/{ => src}/single_field/example.go | 0 testdata/{sort => src/sort_only}/example.go | 2 +- testdata/{ => src}/sortorder/example.go | 0 testdata/{ => src}/strict/example.go | 0 8 files changed, 47 insertions(+), 52 deletions(-) rename testdata/{align => src/align_only}/example.go (100%) rename testdata/{ => src}/alignsortorder/example.go (100%) rename testdata/{ => src}/bad_syntax_tag/example.go (100%) rename testdata/{ => src}/single_field/example.go (100%) rename testdata/{sort => src/sort_only}/example.go (97%) rename testdata/{ => src}/sortorder/example.go (100%) rename testdata/{ => src}/strict/example.go (100%) diff --git a/tagalign_test.go b/tagalign_test.go index 2060dc1..d9f5b71 100644 --- a/tagalign_test.go +++ b/tagalign_test.go @@ -1,7 +1,6 @@ package tagalign import ( - "path/filepath" "testing" "github.com/fatih/structtag" @@ -9,35 +8,55 @@ import ( "golang.org/x/tools/go/analysis/analysistest" ) -func Test_alignOnly(t *testing.T) { - // only align - a := NewAnalyzer() - unsort, err := filepath.Abs("testdata/align") - assert.NoError(t, err) - analysistest.Run(t, unsort, a) -} +func TestAnalyzer(t *testing.T) { + testCases := []struct { + desc string + dir string + opts []Option + }{ + { + desc: "only align", + dir: "align_only", + }, + { + desc: "sort only", + dir: "sort_only", + opts: []Option{WithAlign(false), WithSort(nil...)}, + }, + { + desc: "sort with order", + dir: "sortorder", + opts: []Option{WithAlign(false), WithSort("xml", "json", "yaml")}, + }, + { + desc: "align and sort with fixed order", + dir: "alignsortorder", + opts: []Option{WithSort("json", "yaml", "xml")}, + }, + { + desc: "strict style", + dir: "strict", + opts: []Option{WithSort("json", "yaml", "xml"), WithStrictStyle()}, + }, + { + desc: "align single field", + dir: "single_field", + }, + { + desc: "bad syntax tag", + dir: "bad_syntax_tag", + }, + } -func Test_sortOnly(t *testing.T) { - a := NewAnalyzer(WithAlign(false), WithSort(nil...)) - sort, err := filepath.Abs("testdata/sort") - assert.NoError(t, err) - analysistest.Run(t, sort, a) -} + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() -func Test_sortWithOrder(t *testing.T) { - // test disable align but enable sort - a := NewAnalyzer(WithAlign(false), WithSort("xml", "json", "yaml")) - sort, err := filepath.Abs("testdata/sortorder") - assert.NoError(t, err) - analysistest.Run(t, sort, a) -} + a := NewAnalyzer(test.opts...) -func Test_alignAndSortWithOrder(t *testing.T) { - // align and sort with fixed order - a := NewAnalyzer(WithSort("json", "yaml", "xml")) - sort, err := filepath.Abs("testdata/alignsortorder") - assert.NoError(t, err) - analysistest.Run(t, sort, a) + analysistest.Run(t, analysistest.TestData(), a, test.dir) + }) + } } func Test_alignFormat(t *testing.T) { @@ -57,27 +76,3 @@ func Test_sortTags(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) -} - -func Test_alignSingleField(t *testing.T) { - // only align - a := NewAnalyzer() - unsort, err := filepath.Abs("testdata/single_field") - assert.NoError(t, err) - analysistest.Run(t, unsort, a) -} - -func Test_badSyntaxTag(t *testing.T) { - // only align - a := NewAnalyzer() - unsort, err := filepath.Abs("testdata/bad_syntax_tag") - assert.NoError(t, err) - analysistest.Run(t, unsort, a) -} diff --git a/testdata/align/example.go b/testdata/src/align_only/example.go similarity index 100% rename from testdata/align/example.go rename to testdata/src/align_only/example.go diff --git a/testdata/alignsortorder/example.go b/testdata/src/alignsortorder/example.go similarity index 100% rename from testdata/alignsortorder/example.go rename to testdata/src/alignsortorder/example.go diff --git a/testdata/bad_syntax_tag/example.go b/testdata/src/bad_syntax_tag/example.go similarity index 100% rename from testdata/bad_syntax_tag/example.go rename to testdata/src/bad_syntax_tag/example.go diff --git a/testdata/single_field/example.go b/testdata/src/single_field/example.go similarity index 100% rename from testdata/single_field/example.go rename to testdata/src/single_field/example.go diff --git a/testdata/sort/example.go b/testdata/src/sort_only/example.go similarity index 97% rename from testdata/sort/example.go rename to testdata/src/sort_only/example.go index d298a14..cb82133 100644 --- a/testdata/sort/example.go +++ b/testdata/src/sort_only/example.go @@ -1,4 +1,4 @@ -package sort +package sort_only import "time" diff --git a/testdata/sortorder/example.go b/testdata/src/sortorder/example.go similarity index 100% rename from testdata/sortorder/example.go rename to testdata/src/sortorder/example.go diff --git a/testdata/strict/example.go b/testdata/src/strict/example.go similarity index 100% rename from testdata/strict/example.go rename to testdata/src/strict/example.go From 36263ea5775d73a2b4bc296783bcc503956a4d8f Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Sun, 22 Dec 2024 20:07:23 +0100 Subject: [PATCH 6/7] tests: add suggested fixes --- tagalign_test.go | 4 +--- testdata/src/align_only/example.go.golden | 23 +++++++++++++++++++ testdata/src/alignsortorder/example.go.golden | 13 +++++++++++ testdata/src/bad_syntax_tag/example.go.golden | 17 ++++++++++++++ testdata/src/single_field/example.go.golden | 10 ++++++++ testdata/src/sort_only/example.go.golden | 13 +++++++++++ testdata/src/sortorder/example.go.golden | 12 ++++++++++ testdata/src/strict/example.go.golden | 18 +++++++++++++++ 8 files changed, 107 insertions(+), 3 deletions(-) create mode 100644 testdata/src/align_only/example.go.golden create mode 100644 testdata/src/alignsortorder/example.go.golden create mode 100644 testdata/src/bad_syntax_tag/example.go.golden create mode 100644 testdata/src/single_field/example.go.golden create mode 100644 testdata/src/sort_only/example.go.golden create mode 100644 testdata/src/sortorder/example.go.golden create mode 100644 testdata/src/strict/example.go.golden diff --git a/tagalign_test.go b/tagalign_test.go index d9f5b71..59afebf 100644 --- a/tagalign_test.go +++ b/tagalign_test.go @@ -50,11 +50,9 @@ func TestAnalyzer(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - t.Parallel() - a := NewAnalyzer(test.opts...) - analysistest.Run(t, analysistest.TestData(), a, test.dir) + analysistest.RunWithSuggestedFixes(t, analysistest.TestData(), a, test.dir) }) } } diff --git a/testdata/src/align_only/example.go.golden b/testdata/src/align_only/example.go.golden new file mode 100644 index 0000000..9df290e --- /dev/null +++ b/testdata/src/align_only/example.go.golden @@ -0,0 +1,23 @@ +package testdata + +type FooBar struct { + Foo int `json:"foo" validate:"required"` // want `tag is not aligned, should be: json:"foo"` + Bar string `json:"___bar___,omitempty" validate:"required"` // want `json:"___bar___,omitempty" validate:"required"` + FooFoo int8 `json:"foo_foo" validate:"required" yaml:"fooFoo"` // want `tag is not aligned, should be: json:"foo_foo"` + BarBar int `json:"bar_bar" validate:"required"` // want `tag is not aligned, should be: json:"bar_bar"` + FooBar struct { + Foo int `json:"foo" yaml:"foo" validate:"required"` // want `tag is not aligned, should be: json:"foo" yaml:"foo" validate:"required"` + Bar222 string `json:"bar222" validate:"required" yaml:"bar"` // want `tag is not aligned, should be: json:"bar222" validate:"required" yaml:"bar"` + } `json:"foo_bar" validate:"required"` + FooFooFoo struct { + BarBarBar struct { + BarBarBarBar string `json:"bar_bar_bar_bar" validate:"required"` // want `json:"bar_bar_bar_bar" validate:"required"` + BarBarBarFooBar string `json:"bar_bar_bar_foo_bar" yaml:"bar" validate:"required"` // want `tag is not aligned, should be: json:"bar_bar_bar_foo_bar" yaml:"bar" validate:"required"` + } `json:"bar_bar_bar" validate:"required"` + } + BarFooBarFoo struct{} + // test comment + // test commnet 2 + BarFoo string `json:"bar_foo" validate:"required"` // want `tag is not aligned, should be: json:"bar_foo" validate:"required"` + BarFooBar string `json:"bar_foo_bar" validate:"required"` +} diff --git a/testdata/src/alignsortorder/example.go.golden b/testdata/src/alignsortorder/example.go.golden new file mode 100644 index 0000000..ce63da0 --- /dev/null +++ b/testdata/src/alignsortorder/example.go.golden @@ -0,0 +1,13 @@ +package alignsortorder + +type AlignAndSortWithOrderExample struct { + Foo int `json:"foo,omitempty" yaml:"bar" xml:"baz" binding:"required" gorm:"column:foo" validate:"required" zip:"foo"` // 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 `json:"bar,omitempty" yaml:"foo" xml:"bar" binding:"required" gorm:"column:bar" validate:"required" 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 `json:"bar,omitempty" yaml:"foo" xml:"bar" binding:"required" gorm:"column:bar" validate:"required" 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"` + } + +type AlignAndSortWithOrderExample2 struct { + Foo int `json:"foo,omitempty" yaml:"bar" xml:"baz" binding:"required" gorm:"column:foo" validate:"required" zip:"foo"` // 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 `json:"bar,omitempty" yaml:"foo" xml:"bar" binding:"required" gorm:"column:bar" validate:"required" 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"` +} diff --git a/testdata/src/bad_syntax_tag/example.go.golden b/testdata/src/bad_syntax_tag/example.go.golden new file mode 100644 index 0000000..1422449 --- /dev/null +++ b/testdata/src/bad_syntax_tag/example.go.golden @@ -0,0 +1,17 @@ +package issues6 + +type FooBar struct { + Foo int `json: "foo" validate:"required"` // want `bad syntax for struct tag value` + Bar string `json:bar` // want `bad syntax for struct tag value` + FooFoo int8 `json:"foo_foo" validate:"required"` + BarBar int `json:"bar_bar" validate:"required"` +} + +type FooBar2 struct { + Foo int `json:"foo" validate:"required"` + + FooFoo int8 `json:"foo_foo"` + BarBar int `json:"bar_bar" validate:"required"` + XXX int `json:"xxx" validate:"required"` + Bar string `json:bar` // want `bad syntax for struct tag value` +} diff --git a/testdata/src/single_field/example.go.golden b/testdata/src/single_field/example.go.golden new file mode 100644 index 0000000..ec31e08 --- /dev/null +++ b/testdata/src/single_field/example.go.golden @@ -0,0 +1,10 @@ +package singlefield + +type FooBar struct { + Foo int `json:"foo" validate:"required"` + Bar string `json:"bar" validate:"required"` + + FooFoo int8 `json:"foo_foo" validate:"required"` // want `json:"foo_foo" validate:"required"` + + BarBar int `json:"bar_bar" validate:"required"` +} diff --git a/testdata/src/sort_only/example.go.golden b/testdata/src/sort_only/example.go.golden new file mode 100644 index 0000000..d30bb5b --- /dev/null +++ b/testdata/src/sort_only/example.go.golden @@ -0,0 +1,13 @@ +package sort_only + +import "time" + +type TagAlignExampleSortOnlyKO struct { + Foo time.Time `gorm:"column:foo" json:"foo,omitempty" validate:"required" xml:"foo" yaml:"foo" zip:"foo"` // want `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"` // 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"` +} diff --git a/testdata/src/sortorder/example.go.golden b/testdata/src/sortorder/example.go.golden new file mode 100644 index 0000000..ce9d699 --- /dev/null +++ b/testdata/src/sortorder/example.go.golden @@ -0,0 +1,12 @@ +package sortorder + +type SortWithOrderExample 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" json:"bar,omitempty" yaml:"foo" 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 trim spaces between tags + FooBarFoo int `xml:"bar" json:"bar,omitempty" yaml:"foo" gorm:"column:bar" validate:"required" zip:"bar"` // want `xml:"bar" json:"bar,omitempty" yaml:"foo" gorm:"column:bar" validate:"required" zip:"bar"` +} diff --git a/testdata/src/strict/example.go.golden b/testdata/src/strict/example.go.golden new file mode 100644 index 0000000..6cbb615 --- /dev/null +++ b/testdata/src/strict/example.go.golden @@ -0,0 +1,18 @@ +package strict + +type AlignAndSortWithOrderExample struct { + Foo int `json:"foo,omitempty" yaml:"bar" xml:"baz" binding:"required" gorm:"column:foo" validate:"required" zip:"foo"` // 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 `json:"bar,omitempty" yaml:"foo" xml:"bar" binding:"required" gorm:"column:bar" validate:"required" 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 `json:"bar,omitempty" yaml:"foo" xml:"bar" binding:"required" gorm:"column:bar" validate:"required" 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"` +} + +type AlignAndSortWithOrderExample2 struct { + Foo int ` yaml:"bar" xml:"baz" binding:"required" gorm:"column:foo" validate:"required" zip:"foo"` // want `tag is not aligned, should be: yaml:"bar" xml:"baz" binding:"required" gorm:"column:foo" validate:"required" zip:"foo"` + Bar int `json:"bar,omitempty" yaml:"foo" xml:"bar" binding:"required" gorm:"column:bar" validate:"required"` // 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 ` gorm:"column:foo" zip:"foo"` // want `tag is not aligned, should be: gorm:"column:foo" zip:"foo"` + Bar int `json:"bar,omitempty" yaml:"foo" xml:"barxxxxxxxxxxxx" binding:"required" gorm:"column:bar" validate:"required" zip:"bar"` // 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 `json:"bar,omitempty" yaml:"foo" binding:"required" gorm:"column:bar" validate:"required" zip:"bar"` // want `tag is not aligned, should be: json:"bar,omitempty" yaml:"foo" binding:"required" gorm:"column:bar" validate:"required" zip:"bar"` +} From efd5eb32e7c3ab28cb88f4ed23cc4b87696474e6 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Sun, 22 Dec 2024 20:41:12 +0100 Subject: [PATCH 7/7] tests: add cgo case --- tagalign.go | 18 ++++++++++++++++- tagalign_test.go | 6 ++++++ testdata/src/cgo/cgo.go | 43 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 testdata/src/cgo/cgo.go diff --git a/tagalign.go b/tagalign.go index 8896a14..1d79b35 100644 --- a/tagalign.go +++ b/tagalign.go @@ -4,13 +4,13 @@ import ( "cmp" "fmt" "go/ast" + "go/token" "reflect" "slices" "strconv" "strings" "github.com/fatih/structtag" - "golang.org/x/tools/go/analysis" ) @@ -38,6 +38,13 @@ func NewAnalyzer(options ...Option) *analysis.Analyzer { func Run(pass *analysis.Pass, options ...Option) { for _, f := range pass.Files { + filename := getFilename(pass.Fset, f) + if !strings.HasSuffix(filename, ".go") { + continue + } + + println(filename) + h := &Helper{ style: DefaultStyle, align: true, @@ -378,3 +385,12 @@ func removeField(fields []*ast.Field, index int) []*ast.Field { return append(fields[:index], fields[index+1:]...) } + +func getFilename(fset *token.FileSet, file *ast.File) string { + filename := fset.PositionFor(file.Pos(), true).Filename + if !strings.HasSuffix(filename, ".go") { + return fset.PositionFor(file.Pos(), false).Filename + } + + return filename +} diff --git a/tagalign_test.go b/tagalign_test.go index 59afebf..68189cf 100644 --- a/tagalign_test.go +++ b/tagalign_test.go @@ -57,6 +57,12 @@ func TestAnalyzer(t *testing.T) { } } +func TestAnalyzer_cgo(t *testing.T) { + a := NewAnalyzer() + + analysistest.Run(t, analysistest.TestData(), a, "cgo") +} + func Test_alignFormat(t *testing.T) { format := alignFormat(20) assert.Equal(t, "%-20s", format) diff --git a/testdata/src/cgo/cgo.go b/testdata/src/cgo/cgo.go new file mode 100644 index 0000000..a929e26 --- /dev/null +++ b/testdata/src/cgo/cgo.go @@ -0,0 +1,43 @@ +package cgo + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +type FooBar struct { + Foo int `json:"foo" validate:"required"` // want `tag is not aligned, should be: json:"foo"` + Bar string `json:"___bar___,omitempty" validate:"required"` // want `json:"___bar___,omitempty" validate:"required"` + FooFoo int8 `json:"foo_foo" validate:"required" yaml:"fooFoo"` // want `tag is not aligned, should be: json:"foo_foo"` + BarBar int `json:"bar_bar" validate:"required"` // want `tag is not aligned, should be: json:"bar_bar"` + FooBar struct { + Foo int `json:"foo" yaml:"foo" validate:"required"` // want `tag is not aligned, should be: json:"foo" yaml:"foo" validate:"required"` + Bar222 string `json:"bar222" validate:"required" yaml:"bar"` // want `tag is not aligned, should be: json:"bar222" validate:"required" yaml:"bar"` + } `json:"foo_bar" validate:"required"` + FooFooFoo struct { + BarBarBar struct { + BarBarBarBar string `json:"bar_bar_bar_bar" validate:"required"` // want `json:"bar_bar_bar_bar" validate:"required"` + BarBarBarFooBar string `json:"bar_bar_bar_foo_bar" yaml:"bar" validate:"required"` // want `tag is not aligned, should be: json:"bar_bar_bar_foo_bar" yaml:"bar" validate:"required"` + } `json:"bar_bar_bar" validate:"required"` + } + BarFooBarFoo struct{} + // test comment + // test commnet 2 + BarFoo string `json:"bar_foo" validate:"required"` // want `tag is not aligned, should be: json:"bar_foo" validate:"required"` + BarFooBar string `json:"bar_foo_bar" validate:"required"` +}