diff --git a/.golangci.next.reference.yml b/.golangci.next.reference.yml index f53ddc68fde9..ff015539fabb 100644 --- a/.golangci.next.reference.yml +++ b/.golangci.next.reference.yml @@ -33,6 +33,7 @@ linters: - dupl - dupword - durationcheck + - embeddedstructfieldcheck - err113 - errcheck - errchkjson @@ -142,6 +143,7 @@ linters: - dupl - dupword - durationcheck + - embeddedstructfieldcheck - err113 - errcheck - errchkjson diff --git a/go.mod b/go.mod index 3a5195744126..ca6b23a4880b 100644 --- a/go.mod +++ b/go.mod @@ -74,6 +74,7 @@ require ( github.com/ldez/usetesting v0.4.3 github.com/leonklingele/grouper v1.1.2 github.com/macabu/inamedparam v0.2.0 + github.com/manuelarte/embeddedstructfieldcheck v0.2.1 github.com/manuelarte/funcorder v0.5.0 github.com/maratori/testableexamples v1.0.0 github.com/maratori/testpackage v1.1.1 diff --git a/go.sum b/go.sum index 5aec366fb13e..6c6f2bf9639b 100644 --- a/go.sum +++ b/go.sum @@ -391,6 +391,8 @@ github.com/macabu/inamedparam v0.2.0 h1:VyPYpOc10nkhI2qeNUdh3Zket4fcZjEWe35poddB github.com/macabu/inamedparam v0.2.0/go.mod h1:+Pee9/YfGe5LJ62pYXqB89lJ+0k5bsR8Wgz/C0Zlq3U= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/manuelarte/embeddedstructfieldcheck v0.2.1 h1:oKexdVGs8Jy31IzOD/EMKfZmgogFhAaDbHb1o0qmA1Q= +github.com/manuelarte/embeddedstructfieldcheck v0.2.1/go.mod h1:LSo/IQpPfx1dXMcX4ibZCYA7Yy6ayZHIaOGM70+1Wy8= github.com/manuelarte/funcorder v0.5.0 h1:llMuHXXbg7tD0i/LNw8vGnkDTHFpTnWqKPI85Rknc+8= github.com/manuelarte/funcorder v0.5.0/go.mod h1:Yt3CiUQthSBMBxjShjdXMexmzpP8YGvGLjrxJNkO2hA= github.com/maratori/testableexamples v1.0.0 h1:dU5alXRrD8WKSjOUnmJZuzdxWOEQ57+7s93SLMxb2vI= diff --git a/jsonschema/golangci.next.jsonschema.json b/jsonschema/golangci.next.jsonschema.json index 78960fabeaf1..9da8ac328072 100644 --- a/jsonschema/golangci.next.jsonschema.json +++ b/jsonschema/golangci.next.jsonschema.json @@ -734,6 +734,7 @@ "dupl", "dupword", "durationcheck", + "embeddedstructfieldcheck", "errcheck", "errchkjson", "errname", diff --git a/pkg/golinters/embeddedstructfieldcheck/embeddedstructfieldcheck.go b/pkg/golinters/embeddedstructfieldcheck/embeddedstructfieldcheck.go new file mode 100644 index 000000000000..781bcf5d201e --- /dev/null +++ b/pkg/golinters/embeddedstructfieldcheck/embeddedstructfieldcheck.go @@ -0,0 +1,19 @@ +package embeddedstructfieldcheck + +import ( + "github.com/manuelarte/embeddedstructfieldcheck/analyzer" + "golang.org/x/tools/go/analysis" + + "github.com/golangci/golangci-lint/v2/pkg/goanalysis" +) + +func New() *goanalysis.Linter { + a := analyzer.NewAnalyzer() + + return goanalysis.NewLinter( + a.Name, + a.Doc, + []*analysis.Analyzer{a}, + nil, + ).WithLoadMode(goanalysis.LoadModeSyntax) +} diff --git a/pkg/golinters/embeddedstructfieldcheck/embeddedstructfieldcheck_integration_test.go b/pkg/golinters/embeddedstructfieldcheck/embeddedstructfieldcheck_integration_test.go new file mode 100644 index 000000000000..ba4258467733 --- /dev/null +++ b/pkg/golinters/embeddedstructfieldcheck/embeddedstructfieldcheck_integration_test.go @@ -0,0 +1,19 @@ +package embeddedstructfieldcheck + +import ( + "testing" + + "github.com/golangci/golangci-lint/v2/test/testshared/integration" +) + +func TestFromTestdata(t *testing.T) { + integration.RunTestdata(t) +} + +func TestFix(t *testing.T) { + integration.RunFix(t) +} + +func TestFixPathPrefix(t *testing.T) { + integration.RunFixPathPrefix(t) +} diff --git a/pkg/golinters/embeddedstructfieldcheck/testdata/embeddedstructfieldcheck_comments.go b/pkg/golinters/embeddedstructfieldcheck/testdata/embeddedstructfieldcheck_comments.go new file mode 100644 index 000000000000..12a6a4fa1ddf --- /dev/null +++ b/pkg/golinters/embeddedstructfieldcheck/testdata/embeddedstructfieldcheck_comments.go @@ -0,0 +1,27 @@ +//golangcitest:args -Eembeddedstructfieldcheck +package simple + +import "time" + +type ValidStructWithSingleLineComments struct { + // time.Time Single line comment + time.Time + + // version Single line comment + version int +} + +type StructWithSingleLineComments struct { + // time.Time Single line comment + time.Time // want `there must be an empty line separating embedded fields from regular fields` + // version Single line comment + version int +} + +type StructWithMultiLineComments struct { + // time.Time Single line comment + time.Time // want `there must be an empty line separating embedded fields from regular fields` + // version Single line comment + // very long comment + version int +} diff --git a/pkg/golinters/embeddedstructfieldcheck/testdata/embeddedstructfieldcheck_simple.go b/pkg/golinters/embeddedstructfieldcheck/testdata/embeddedstructfieldcheck_simple.go new file mode 100644 index 000000000000..47b155e5f52a --- /dev/null +++ b/pkg/golinters/embeddedstructfieldcheck/testdata/embeddedstructfieldcheck_simple.go @@ -0,0 +1,39 @@ +//golangcitest:args -Eembeddedstructfieldcheck +package simple + +import ( + "context" + "time" +) + +type ValidStruct struct { + time.Time + + version int +} + +type NoSpaceStruct struct { + time.Time // want `there must be an empty line separating embedded fields from regular fields` + version int +} + +type NotSortedStruct struct { + version int + + time.Time // want `embedded fields should be listed before regular fields` +} + +type MixedEmbeddedAndNotEmbedded struct { + context.Context + + name string + + time.Time // want `embedded fields should be listed before regular fields` + + age int +} + +type EmbeddedWithPointers struct { + *time.Time // want `there must be an empty line separating embedded fields from regular fields` + version int +} diff --git a/pkg/golinters/embeddedstructfieldcheck/testdata/embeddedstructfieldcheck_special_cases.go b/pkg/golinters/embeddedstructfieldcheck/testdata/embeddedstructfieldcheck_special_cases.go new file mode 100644 index 000000000000..95b5356b5824 --- /dev/null +++ b/pkg/golinters/embeddedstructfieldcheck/testdata/embeddedstructfieldcheck_special_cases.go @@ -0,0 +1,11 @@ +//golangcitest:args -Eembeddedstructfieldcheck +package simple + +import "time" + +func myFunction() { + type myType struct { + version int + time.Time // want `embedded fields should be listed before regular fields` + } +} diff --git a/pkg/golinters/embeddedstructfieldcheck/testdata/fix/in/comments.go b/pkg/golinters/embeddedstructfieldcheck/testdata/fix/in/comments.go new file mode 100644 index 000000000000..5cd45dc171b7 --- /dev/null +++ b/pkg/golinters/embeddedstructfieldcheck/testdata/fix/in/comments.go @@ -0,0 +1,39 @@ +//golangcitest:args -Eembeddedstructfieldcheck +//golangcitest:expected_exitcode 0 +package testdata + +import "time" + +type ValidStructWithSingleLineComments struct { + // time.Time Single line comment + time.Time + + // version Single line comment + version int +} + +type StructWithSingleLineComments struct { + // time.Time Single line comment + time.Time // want `there must be an empty line separating embedded fields from regular fields` + + // version Single line comment + version int +} + +type StructWithMultiLineComments struct { + // time.Time Single line comment + time.Time // want `there must be an empty line separating embedded fields from regular fields` + + // version Single line comment + // very long comment + version int +} + +type A struct { + // comment + ValidStructWithSingleLineComments + // C is foo + StructWithSingleLineComments // want `there must be an empty line separating embedded fields from regular fields` + + D string +} diff --git a/pkg/golinters/embeddedstructfieldcheck/testdata/fix/in/simple.go b/pkg/golinters/embeddedstructfieldcheck/testdata/fix/in/simple.go new file mode 100644 index 000000000000..578ddc0d170e --- /dev/null +++ b/pkg/golinters/embeddedstructfieldcheck/testdata/fix/in/simple.go @@ -0,0 +1,23 @@ +//golangcitest:args -Eembeddedstructfieldcheck +//golangcitest:expected_exitcode 0 +package testdata + +import ( + "time" +) + +type ValidStruct struct { + time.Time + + version int +} + +type NoSpaceStruct struct { + time.Time // want `there must be an empty line separating embedded fields from regular fields` + version int +} + +type EmbeddedWithPointers struct { + *time.Time // want `there must be an empty line separating embedded fields from regular fields` + version int +} diff --git a/pkg/golinters/embeddedstructfieldcheck/testdata/fix/out/comments.go b/pkg/golinters/embeddedstructfieldcheck/testdata/fix/out/comments.go new file mode 100644 index 000000000000..5cd45dc171b7 --- /dev/null +++ b/pkg/golinters/embeddedstructfieldcheck/testdata/fix/out/comments.go @@ -0,0 +1,39 @@ +//golangcitest:args -Eembeddedstructfieldcheck +//golangcitest:expected_exitcode 0 +package testdata + +import "time" + +type ValidStructWithSingleLineComments struct { + // time.Time Single line comment + time.Time + + // version Single line comment + version int +} + +type StructWithSingleLineComments struct { + // time.Time Single line comment + time.Time // want `there must be an empty line separating embedded fields from regular fields` + + // version Single line comment + version int +} + +type StructWithMultiLineComments struct { + // time.Time Single line comment + time.Time // want `there must be an empty line separating embedded fields from regular fields` + + // version Single line comment + // very long comment + version int +} + +type A struct { + // comment + ValidStructWithSingleLineComments + // C is foo + StructWithSingleLineComments // want `there must be an empty line separating embedded fields from regular fields` + + D string +} diff --git a/pkg/golinters/embeddedstructfieldcheck/testdata/fix/out/simple.go b/pkg/golinters/embeddedstructfieldcheck/testdata/fix/out/simple.go new file mode 100644 index 000000000000..a35f19ff4a15 --- /dev/null +++ b/pkg/golinters/embeddedstructfieldcheck/testdata/fix/out/simple.go @@ -0,0 +1,25 @@ +//golangcitest:args -Eembeddedstructfieldcheck +//golangcitest:expected_exitcode 0 +package testdata + +import ( + "time" +) + +type ValidStruct struct { + time.Time + + version int +} + +type NoSpaceStruct struct { + time.Time // want `there must be an empty line separating embedded fields from regular fields` + + version int +} + +type EmbeddedWithPointers struct { + *time.Time // want `there must be an empty line separating embedded fields from regular fields` + + version int +} diff --git a/pkg/lint/lintersdb/builder_linter.go b/pkg/lint/lintersdb/builder_linter.go index c975549cf6ba..37f18bdc9eae 100644 --- a/pkg/lint/lintersdb/builder_linter.go +++ b/pkg/lint/lintersdb/builder_linter.go @@ -19,6 +19,7 @@ import ( "github.com/golangci/golangci-lint/v2/pkg/golinters/dupl" "github.com/golangci/golangci-lint/v2/pkg/golinters/dupword" "github.com/golangci/golangci-lint/v2/pkg/golinters/durationcheck" + "github.com/golangci/golangci-lint/v2/pkg/golinters/embeddedstructfieldcheck" "github.com/golangci/golangci-lint/v2/pkg/golinters/err113" "github.com/golangci/golangci-lint/v2/pkg/golinters/errcheck" "github.com/golangci/golangci-lint/v2/pkg/golinters/errchkjson" @@ -212,6 +213,10 @@ func (LinterBuilder) Build(cfg *config.Config) ([]*linter.Config, error) { WithLoadForGoAnalysis(). WithURL("https://github.com/charithe/durationcheck"), + linter.NewConfig(embeddedstructfieldcheck.New()). + WithSince("v2.2.0"). + WithURL("https://github.com/manuelarte/embeddedstructfieldcheck"), + linter.NewConfig(errcheck.New(&cfg.Linters.Settings.Errcheck)). WithGroups(config.GroupStandard). WithSince("v1.0.0").