diff --git a/README.md b/README.md index 11810f905..fc4d5b3a4 100644 --- a/README.md +++ b/README.md @@ -452,6 +452,7 @@ List of all available rules. The rules ported from `golint` are left unchanged a | [`defer`](./RULES_DESCRIPTIONS.md#defer) | map | Warns on some [defer gotchas](https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-iii-36a1ab3d6ef1) | no | no | | [`unexported-naming`](./RULES_DESCRIPTIONS.md#unexported-naming) | n/a | Warns on wrongly named un-exported symbols | no | no | | [`function-length`](./RULES_DESCRIPTIONS.md#function-length) | n/a | Warns on functions exceeding the statements or lines max | no | no | +| [`nested-structs`](./RULES_DESCRIPTIONS.md#nested-structs) | n/a | Warns on structs within structs | no | no | ## Configurable rules diff --git a/RULES_DESCRIPTIONS.md b/RULES_DESCRIPTIONS.md index f1bd38978..2a8a0934f 100644 --- a/RULES_DESCRIPTIONS.md +++ b/RULES_DESCRIPTIONS.md @@ -45,6 +45,7 @@ List of all available rules. - [max-public-structs](#max-public-structs) - [modifies-parameter](#modifies-parameter) - [modifies-value-receiver](#modifies-value-receiver) + - [nested-structs](#nested-structs) - [package-comments](#package-comments) - [range](#range) - [range-val-in-closure](#range-val-in-closure) @@ -444,6 +445,12 @@ This rule warns when a method modifies its receiver. _Configuration_: N/A +## nested-structs + +_Description_: Packages declaring structs that contain other inline struct definitions can be hard to understand/read for other developers. + +_Configuration_: N/A + ## package-comments _Description_: Packages should have comments. This rule warns on undocumented packages and when packages comments are detached to the `package` keyword. diff --git a/config/config.go b/config/config.go index 3231434b1..6b010038e 100644 --- a/config/config.go +++ b/config/config.go @@ -79,6 +79,7 @@ var allRules = append([]lint.Rule{ &rule.DeferRule{}, &rule.UnexportedNamingRule{}, &rule.FunctionLength{}, + &rule.NestedStructs{}, }, defaultRules...) var allFormatters = []lint.Formatter{ diff --git a/rule/nested-structs.go b/rule/nested-structs.go new file mode 100644 index 000000000..cfe9648b2 --- /dev/null +++ b/rule/nested-structs.go @@ -0,0 +1,61 @@ +package rule + +import ( + "go/ast" + + "github.com/mgechev/revive/lint" +) + +// NestedStructs lints nested structs. +type NestedStructs struct{} + +// Apply applies the rule to given file. +func (r *NestedStructs) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { + var failures []lint.Failure + + if len(arguments) > 0 { + panic(r.Name() + " doesn't take any arguments") + } + + walker := &lintNestedStructs{ + fileAST: file.AST, + onFailure: func(failure lint.Failure) { + failures = append(failures, failure) + }, + } + + ast.Walk(walker, file.AST) + + return failures +} + +// Name returns the rule name. +func (r *NestedStructs) Name() string { + return "nested-structs" +} + +type lintNestedStructs struct { + fileAST *ast.File + onFailure func(lint.Failure) +} + +func (l *lintNestedStructs) Visit(n ast.Node) ast.Visitor { + switch v := n.(type) { + case *ast.FuncDecl: + if v.Body != nil { + ast.Walk(l, v.Body) + } + return nil + case *ast.Field: + if _, ok := v.Type.(*ast.StructType); ok { + l.onFailure(lint.Failure{ + Failure: "no nested structs are allowed", + Category: "style", + Node: v, + Confidence: 1, + }) + break + } + } + return l +} diff --git a/test/nested-structs_test.go b/test/nested-structs_test.go new file mode 100644 index 000000000..733521a39 --- /dev/null +++ b/test/nested-structs_test.go @@ -0,0 +1,12 @@ +package test + +import ( + "testing" + + "github.com/mgechev/revive/lint" + "github.com/mgechev/revive/rule" +) + +func TestNestedStructs(t *testing.T) { + testRule(t, "nested-structs", &rule.NestedStructs{}, &lint.RuleConfig{}) +} diff --git a/testdata/nested-structs.go b/testdata/nested-structs.go new file mode 100644 index 000000000..3c6a97416 --- /dev/null +++ b/testdata/nested-structs.go @@ -0,0 +1,32 @@ +package fixtures + +type Foo struct { + Bar struct { // MATCH /no nested structs are allowed/ + Baz struct { // MATCH /no nested structs are allowed/ + b bool + Qux struct { // MATCH /no nested structs are allowed/ + b bool + } + } + } +} + +type Quux struct { + Quuz Quuz +} + +type Quuz struct { +} + +func waldo() (s struct{ b bool }) { return s } + +func fred() interface{} { + s := struct { + b bool + t struct { // MATCH /no nested structs are allowed/ + b bool + } + }{} + + return s +}