Repository: bombsimon/wsl (go)

Issue:
Not all blocks are parsed
Must be more generous about when to enter a block and parse it.

**Gives no error but should**

```go
var foo string

func() {
    var err error
    foo = "1"
}
```

**Gives error if assigned to variable**

```go
var foo string

x := func() {
    var err error
    foo = "1"
}
```

> assignments should only be cuddled with other assignments

Also this gives no error, assignign or not:

```go
var err error
foo, err = func() { return "", nil }
```

Pull request description:
Better support to find blocks
Recursively find block bodies which needs to be parsed other than if,
range, for and already covered types.

Resolves #52 

Patch:
diff --git a/wsl.go b/wsl.go
index 8699bbc..8a1f45b 100644
--- a/wsl.go
+++ b/wsl.go
@@ -207,14 +207,12 @@ func (p *Processor) parseBlockBody(ident *ast.Ident, block *ast.BlockStmt) {
 // nolint: gocognit
 func (p *Processor) parseBlockStatements(statements []ast.Stmt) {
 	for i, stmt := range statements {
-		// TODO: How to tell when and where func literals may exist to enforce
-		// linting.
-		if as, isAssignStmt := stmt.(*ast.AssignStmt); isAssignStmt {
-			for _, rhs := range as.Rhs {
-				if fl, isFuncLit := rhs.(*ast.FuncLit); isFuncLit {
-					p.parseBlockBody(nil, fl.Body)
-				}
-			}
+		// Start by checking if this statement is another block (other than if,
+		// for and range). This could be assignment to a function, defer or go
+		// call with an inline function or similar. If this is found we start by
+		// parsing this body block before moving on.
+		for _, stmtBlocks := range p.findBlockStmt(stmt) {
+			p.parseBlockBody(nil, stmtBlocks)
 		}
 
 		firstBodyStatement := p.firstBodyStatement(i, statements)
@@ -731,6 +729,35 @@ func (p *Processor) findRHS(node ast.Node) []string {
 	return rhs
 }
 
+func (p *Processor) findBlockStmt(node ast.Node) []*ast.BlockStmt {
+	var blocks []*ast.BlockStmt
+
+	switch t := node.(type) {
+	case *ast.AssignStmt:
+		for _, x := range t.Rhs {
+			blocks = append(blocks, p.findBlockStmt(x)...)
+		}
+	case *ast.CallExpr:
+		blocks = append(blocks, p.findBlockStmt(t.Fun)...)
+	case *ast.FuncLit:
+		blocks = append(blocks, t.Body)
+	case *ast.ExprStmt:
+		blocks = append(blocks, p.findBlockStmt(t.X)...)
+	case *ast.ReturnStmt:
+		for _, x := range t.Results {
+			blocks = append(blocks, p.findBlockStmt(x)...)
+		}
+	case *ast.DeferStmt:
+		blocks = append(blocks, p.findBlockStmt(t.Call)...)
+	case *ast.GoStmt:
+		blocks = append(blocks, p.findBlockStmt(t.Call)...)
+	default:
+		// spew.Dump(t)
+	}
+
+	return blocks
+}
+
 // maybeX extracts the X field from an AST node and returns it with a true value
 // if it exists. If the node doesn't have an X field nil and false is returned.
 // Known fields with X that are handled:


Test patch:
diff --git a/wsl_test.go b/wsl_test.go
index 726494d..b92bf5a 100644
--- a/wsl_test.go
+++ b/wsl_test.go
@@ -1137,6 +1137,66 @@ func TestShouldAddEmptyLines(t *testing.T) {
 				"if statements should only be cuddled with assignments used in the if statement itself",
 			},
 		},
+		{
+			description: "ensure blocks are found and checked for errors",
+			code: []byte(`package main
+
+				func main() {
+					var foo string
+
+					x := func() {
+						var err error
+						foo = "1"
+					}()
+
+					x := func() {
+						var err error
+						foo = "1"
+					}
+
+					func() {
+						var err error
+						foo = "1"
+					}()
+
+					func() {
+						var err error
+						foo = "1"
+					}
+
+					func() {
+						func() {
+							return func() {
+								var err error
+								foo = "1"
+							}
+						}()
+					}
+
+					var x error
+					foo, err := func() { return "", nil }
+
+					defer func() {
+						var err error
+						foo = "1"
+					}()
+
+					go func() {
+						var err error
+						foo = "1"
+					}()
+				}`),
+			expectedErrorStrings: []string{
+				"assignments should only be cuddled with other assignments",
+				"assignments should only be cuddled with other assignments",
+				"assignments should only be cuddled with other assignments",
+				"assignments should only be cuddled with other assignments",
+				"assignments should only be cuddled with other assignments",
+				"assignments should only be cuddled with other assignments",
+				"assignments should only be cuddled with other assignments",
+				"assignments should only be cuddled with other assignments",
+			},
+		},
 	}
 
 	for _, tc := range cases {