Repository: bombsimon/wsl
Pull request #72 (2020-02-24 22:05:24)

Add fixer
This is a tracking pull request for the implementation of a fixer. This PR aims to solve #4 

- [x] Add newline when linter says to cuddled
- [x] Add newline when linter says to cuddled - support comments
- [x] Don't add newline if a previous fix resolves multi cuddle<sup>1</sup>
- [x] Add newline in end of case block when enforced
- [x] Add newline at proper place when considering errors (see example below)
- [x] Remove newline in beginning of block
- [x] Remove newline in beginning of block (body) - support comments
- [x] Remove newline in end of block
- [x] Remove newline in end of block (body) - support comments
- [x] Remove newline when enforce to cuddle error checking
- [x] ~Advanced formatting, e.g. group `var`~
- [x] Remove newline in beginning of block (case) - support comments
- [x] Remove newline in beginning of block (case) - support comments

<sup>1</sup>Since only one assignment/statement is allowed before if checks the linter will warn if blocks are cuddled. The fixer will resolve the block cuddling and thus the fixer should not add proposals to fix the check after. Example:

**This**

```go
if true {
    return
}
_, err := doSomething()
if err != nil {
    return
}
```

**Should be resolved to this**

```go
if true {
    return
}

_, err := doSomething()
if err != nil {
    return
}
```

**But is currently resolved to this**

```go
if true {
    return
}

_, err := doSomething()

if err != nil {
    return
}
```

coveralls: ## Pull Request Test Coverage Report for [Build 177](https://coveralls.io/builds/28932481)

* **51** of **106**   **(48.11%)**  changed or added relevant lines in **2** files are covered.
* No unchanged relevant lines lost coverage.
* Overall coverage decreased (**-5.5%**) to **83.952%**

---

|  Changes Missing Coverage | Covered Lines | Changed/Added Lines | % |
| :-----|--------------|--------|---: |
| [wsl.go](https://coveralls.io/builds/28932481/source?filename=wsl.go#L19) | 51 | 66 | 77.27%
| [analyzer.go](https://coveralls.io/builds/28932481/source?filename=analyzer.go#L18) | 0 | 40 | 0.0%
<!-- | **Total:** | **51** | **106** | **48.11%** | -->


|  Totals | [![Coverage Status](https://coveralls.io/builds/28932481/badge)](https://coveralls.io/builds/28932481) |
| :-- | --: |
| Change from base [Build 177](https://coveralls.io/builds/28917980): |  -5.5% |
| Covered Lines: | 565 |
| Relevant Lines: | 673 |

---
##### 💛  - [Coveralls](https://coveralls.io)

dcu: this would be a great feature
bombsimon: I'm not looking forward to support removal of lines and not mess up comments in the current state. I'm not sure if [whitespace](https://github.com/ultraware/whitespace) will resolve errors 1:1 with how this linter want's it but at least it will fix obvious empty lines. I think > 99% are using this linter with golangci-lint and if so using `--fix` will run both fixers. Based on this I'm considering to not implement any whitespace removal in the current state, at least not in the beginning or end of block.
bombsimon: Just found out about https://github.com/mvdan/gofumpt which overlaps a lot of features here, many removing newlines. Might be some help figuring out a way forward for this. Although this has been stale manly because I haven't had the time to work on it.
Davincible: What's the current state of this?

bombsimon: > What's the current state of this?

Sadly unchanged from last time and the note in #90 and I consider the project mostly in a maintenance state. I haven't really gotten much feedback or request so motivation has been low. I actually started some rework in a [v4.0.0](https://github.com/bombsimon/wsl/commits/v4.0.0) branch this year but I've been too focused on other things to give this the attention needed. The idea with the v4 branch was to rebuild a lot of things from scratch to be more confident around the fixer but I never kept working on it.

Any help or PR in the right direction would be appreciated but I think my biggest problem is that I don't want to release a fixer that doesn't fix 100% of the cases and until I have that in place there will be no fixer in wsl. I do however think about it every now and then so I can't say it will never happen, although the fact that I haven't gotten to it in 2,5 yeas says otherwise...
dgocoder: @bombsimon we love this linter at work but are getting tired of fixing the issues manually specifically (cuddling) which seems you have working based off the list above. Wondering if any progress has been made since you last wrote? If not we would love to help if possible.
mgreg90: This feature would be great. How can we help this progress?
bombsimon: Hey, glad to hear you're finding this linter useful and want to contribute! And sorry for not pushing this issue more forward.

So first off, not much has changed and the reasons are the same; a combination of time and motivation. The motivation is mostly based on the assumption not many people use this linter and that I also no longer use this at my current workplace so there's no natural maintenance happening. Also since 2018 there's only 18 👍 on #4.

A second reason for little progress is probably [Second-system effect](https://en.wikipedia.org/wiki/Second-system_effect). I created the [v4.0.0 branch](https://github.com/bombsimon/wsl/tree/v4.0.0) back in 2020 and have been tinkering on and off trying to address all open issues but every time I instead get exhausted by starting over. There's also the discussion (oh well, an issue with just a single reaction) in #110 about dropping support to make it easier to land this. After some thinking this would mostly be things that's not really related to whitespaces and mentioned under `Advanced formatting` in this issue, f.ex. [declarations should never be cuddled](https://github.com/bombsimon/wsl/blob/master/doc/rules.md#declarations-should-never-be-cuddled).

Hearing you offering to help is very appreciated and makes me think about what would need to happen to land this. I think for now the roadmap should _start_ with:

* Give up every idea around `v4.0.0` and fixing existing issues for now
* Rebasing this PR on `master`, fix all conflicts and port all missing features to ensure all the current rules work with the `analysis.Analyzer`
* Add support to unit-test expected output

These three (or at least first two) things is probably something I can try to address fairly soon so we have a better starting point. Let me give this a go and feel free to ping me next week if no progress is made in this PR. If you know any good way or framework to test expected output (f.ex. a golden file) please let me know!

If we end up there there's a few things to figure out:

### Whitespace parsing in beginning and end of block

This has become more complex than intended given all the hard requirements of `wsl`. F.ex. `wsl` supports [allow-trailing-comment](https://github.com/bombsimon/wsl/blob/master/doc/configuration.md#allow-trailing-comment) and [allow-separated-leading-comment](https://github.com/bombsimon/wsl/blob/master/doc/configuration.md#allow-separated-leading-comment). On top of that there's special handling of [Example functions](https://github.com/bombsimon/wsl/blob/master/wsl.go#L1023). Basically [`findLeadingAndTrailingWhitespaces`](https://github.com/bombsimon/wsl/blob/master/wsl.go#L1023) is a mess to say the least. What's needed here is to ensure we have all the comments and groups related to their nodes (comments is a bit messy in the ast) and then just write all the logic. PR to improve handling of this would probably help.

### Advanced formatting

This is things like grouping multiple `var` blocks or shuffling around [append calls](https://github.com/bombsimon/wsl/blob/master/doc/rules.md#append-only-allowed-to-cuddle-with-appended-value) when possible. I think there's two or three options for this one:

1. Don't provide a fixer. I don't know if this is reasonable but a fixer could be a best effort. I said before I only want to add a fixer if it covers 100% of the cases but I'm open to re-evaluate that. We would still fix probably a good >90% of the cases.
2. Drop support for these kind of things at the same time as the fixer is implemented. To be fair the whole `var` block rule feels a bit misplaced in this linter and is also already supported (with fixer) in [`gofumpt`](https://github.com/mvdan/gofumpt)
3. Be naive and just add a whitespace. Instead of changing 
<table>
<tr><th>from</th><th>to</th></tr>
<tr><td>

```go
var foo = 1
var bar = 2
bar biz

x := []string{}
x = append(x, "literal")
notUsed := "just assigning, don't mind me"
x = append(x, "not z..")
useMe := "right away"
alsoNotUsed := ":("
x = append(x, "just noise"
x = append(x, useMe)
```

</td><td>

```go
var (
  foo = 1
  bar = 2
  biz
)

notUsed := "just assigning, don't mind me"
alsoNotUsed := ":("

x := []string{}
x = append(x, "literal")
x = append(x, "not z..")
x = append(x, "just noise"
useMe := "to be used"
x = append(x, useMe))
```

</td>
<tr></table>

We would just add whitespaces:

<table>
<tr><th>from</th><th>to</th></tr>
<tr><td>

```go
var foo = 1
var bar = 2
var biz

x := []string{}
x = append(x, "literal")
notUsed := "just assigning, don't mind me"
x = append(x, "not z..")
useMe := "right away"
alsoNotUsed := ":("
x = append(x, "just noise"
x = append(x, useMe)
```

</td><td>

```go
var foo = 1

var bar = 2

var biz

x := []string{}
x = append(x, "literal")

notUsed := "just assigning, don't mind me"

x = append(x, "not z..")

useMe := "right away"
alsoNotUsed := ":("

x = append(x, "just noise"
x = append(x, useMe)
```

</td>
<tr></table>

### Testing

Lastly as mentioned in https://github.com/bombsimon/wsl/issues/90#issuecomment-684436609 I think it's scary to modify other's source code and I can't guarantee everyone has backups or use revision control. I think just help with testing this and finding edge cases would help a lot.

Patch:
diff --git a/README.md b/README.md
index 8ff7439..f507d01 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# WSL - Whitespace Linter
+# wsl - Whitespace Linter
 
 [![forthebadge](https://forthebadge.com/images/badges/made-with-go.svg)](https://forthebadge.com)
 [![forthebadge](https://forthebadge.com/images/badges/built-with-love.svg)](https://forthebadge.com)
@@ -6,83 +6,54 @@
 [![GitHub Actions](https://github.com/bombsimon/wsl/actions/workflows/go.yml/badge.svg)](https://github.com/bombsimon/wsl/actions/workflows/go.yml)
 [![Coverage Status](https://coveralls.io/repos/github/bombsimon/wsl/badge.svg?branch=master)](https://coveralls.io/github/bombsimon/wsl?branch=master)
 
-WSL is a linter that enforces a very **non scientific** vision of how to make
+`wsl` is a linter that enforces a very **non scientific** vision of how to make
 code more readable by enforcing empty lines at the right places.
 
-I think too much code out there is to cuddly and a bit too warm for it's own
-good, making it harder for other people to read and understand. The linter will
-warn about newlines in and around blocks, in the beginning of files and other
-places in the code.
-
-**I know this linter is aggressive** and a lot of projects I've tested it on
-have failed miserably. For this linter to be useful at all I want to be open to
-new ideas, configurations and discussions! Also note that some of the warnings
-might be bugs or unintentional false positives so I would love an
+**This linter is aggressive** and a lot of projects I've tested it on have
+failed miserably. For this linter to be useful at all I want to be open to new
+ideas, configurations and discussions! Also note that some of the warnings might
+be bugs or unintentional false positives so I would love an
 [issue](https://github.com/bombsimon/wsl/issues/new) to fix, discuss, change or
 make something configurable!
 
 ## Installation
 
-### By `go get` (local installation)
-
-You can do that by using:
-
 ```sh
-go get -u github.com/bombsimon/wsl/v3/cmd/...
+go get github.com/bombsimon/wsl/cmd/...
 ```
 
-### By golangci-lint (CI automation)
-
-`wsl` is already integrated with
-[golangci-lint](https://github.com/golangci/golangci-lint). Please refer to the
-instructions there.
-
 ## Usage
 
-How to use depends on how you install `wsl`.
-
-### With local binary
+> **Note**: This linter provides a fixer that can fix most issues with the
+`--fix` flag. However, currently `golangci-lint` [does not support suggested
+fixes](https://github.com/golangci/golangci-lint/issues/1779) so the `--fix`
+flag in `golangci-lint` will **not** work.
 
-The general command format for `wsl` is:
+`wsl` uses the [analysis](https://pkg.go.dev/golang.org/x/tools/go/analysis)
+package meaning it will operate on package level with the default analysis flags
+and way of working.
 
 ```sh
-$ wsl [flags] <file1> [files...]
-$ wsl [flags] </path/to/package/...>
+wsl --help
+wsl [flags] </path/to/package/...>
 
-# Examples
-
-$ wsl ./main.go
-$ wsl --no-test ./main.go
-$ wsl --allow-cuddle-declarations ./main.go
-$ wsl --no-test --allow-cuddle-declaration ./main.go
-$ wsl --no-test --allow-trailing-comment ./myProject/...
+wsl --allow-cuddle-declarations --fix ./...
 ```
 
-The "..." wildcard is not used like other `go` commands but instead can only
-be to a relative or absolute path.
-
-By default, the linter will run on `./...` which means all go files in the
-current path and all subsequent paths, including test files. To disable linting
-test files, use `-n` or `--no-test`.
-
-### By `golangci-lint` (CI automation)
-
-The recommended command is:
+`wsl` is also integrated in [`golangci-lint`](https://golangci-lint.run)
 
 ```sh
-golangci-lint run --disable-all --enable wsl
+golangci-lint run --no-config --disable-all --enable wsl
 ```
 
-For more information, please refer to
-[golangci-lint](https://github.com/golangci/golangci-lint)'s documentation.
-
 ## Issues and configuration
 
 The linter suppers a few ways to configure it to satisfy more than one kind of
 code style. These settings could be set either with flags or with YAML
 configuration if used via `golangci-lint`.
 
-The supported configuration can be found [in the documentation](doc/configuration.md).
+The supported configuration can be found [in the
+documentation](doc/configuration.md).
 
 Below are the available checklist for any hit from `wsl`. If you do not see any,
 feel free to raise an [issue](https://github.com/bombsimon/wsl/issues/new).
@@ -106,14 +77,11 @@ feel free to raise an [issue](https://github.com/bombsimon/wsl/issues/new).
 * [For statements should only be cuddled with assignments used in the iteration](doc/rules.md#for-statements-should-only-be-cuddled-with-assignments-used-in-the-iteration)
 * [Go statements can only invoke functions assigned on line above](doc/rules.md#go-statements-can-only-invoke-functions-assigned-on-line-above)
 * [If statements should only be cuddled with assignments](doc/rules.md#if-statements-should-only-be-cuddled-with-assignments)
-* [If statements should only be cuddled with assignments used in the if
-  statement
-  itself](doc/rules.md#if-statements-should-only-be-cuddled-with-assignments-used-in-the-if-statement-itself)
+* [If statements should only be cuddled with assignments used in the if statement itself](doc/rules.md#if-statements-should-only-be-cuddled-with-assignments-used-in-the-if-statement-itself)
 * [If statements that check an error must be cuddled with the statement that assigned the error](doc/rules.md#if-statements-that-check-an-error-must-be-cuddled-with-the-statement-that-assigned-the-error)
-* [Only cuddled expressions if assigning variable or using from line
-  above](doc/rules.md#only-cuddled-expressions-if-assigning-variable-or-using-from-line-above)
+* [Only cuddled expressions if assigning variable or using from line above](doc/rules.md#only-cuddled-expressions-if-assigning-variable-or-using-from-line-above)
 * [Only one cuddle assignment allowed before defer statement](doc/rules.md#only-one-cuddle-assignment-allowed-before-defer-statement)
-* [Only one cuddle assginment allowed before for statement](doc/rules.md#only-one-cuddle-assignment-allowed-before-for-statement)
+* [Only one cuddle assignment allowed before for statement](doc/rules.md#only-one-cuddle-assignment-allowed-before-for-statement)
 * [Only one cuddle assignment allowed before go statement](doc/rules.md#only-one-cuddle-assignment-allowed-before-go-statement)
 * [Only one cuddle assignment allowed before if statement](doc/rules.md#only-one-cuddle-assignment-allowed-before-if-statement)
 * [Only one cuddle assignment allowed before range statement](doc/rules.md#only-one-cuddle-assignment-allowed-before-range-statement)
diff --git a/analyzer.go b/analyzer.go
new file mode 100644
index 0000000..16dfaf2
--- /dev/null
+++ b/analyzer.go
@@ -0,0 +1,115 @@
+package wsl
+
+import (
+	"flag"
+	"strings"
+
+	"golang.org/x/tools/go/analysis"
+)
+
+// Analyzer is the wsl analyzer.
+//
+//nolint:gochecknoglobals // Analyzers tend to be global.
+var Analyzer = &analysis.Analyzer{
+	Name:             "wsl",
+	Doc:              "add or remove empty lines",
+	Flags:            flags(),
+	Run:              run,
+	RunDespiteErrors: true,
+}
+
+//nolint:gochecknoglobals // Config needs to be overwritten with flags.
+var config = Configuration{
+	AllowAssignAndAnythingCuddle:     false,
+	AllowAssignAndCallCuddle:         true,
+	AllowCuddleDeclaration:           false,
+	AllowMultiLineAssignCuddle:       true,
+	AllowSeparatedLeadingComment:     false,
+	AllowTrailingComment:             false,
+	ForceCuddleErrCheckAndAssign:     false,
+	ForceExclusiveShortDeclarations:  false,
+	StrictAppend:                     true,
+	AllowCuddleWithCalls:             []string{"Lock", "RLock"},
+	AllowCuddleWithRHS:               []string{"Unlock", "RUnlock"},
+	ErrorVariableNames:               []string{"err"},
+	ForceCaseTrailingWhitespaceLimit: 0,
+}
+
+func flags() flag.FlagSet {
+	flags := flag.NewFlagSet("", flag.ExitOnError)
+
+	flags.BoolVar(&config.AllowAssignAndAnythingCuddle, "allow-assign-and-anything", false, "Allow assignments and anything to be cuddled")
+	flags.BoolVar(&config.AllowAssignAndCallCuddle, "allow-assign-and-call", true, "Allow assignments and calls to be cuddled (if using same variable/type)")
+	flags.BoolVar(&config.AllowCuddleDeclaration, "allow-cuddle-declarations", false, "Allow declarations to be cuddled")
+	flags.BoolVar(&config.AllowMultiLineAssignCuddle, "allow-multi-line-assign", true, "Allow cuddling with multi line assignments")
+	flags.BoolVar(&config.AllowSeparatedLeadingComment, "allow-separated-leading-comment", false, "Allow empty newlines in leading comments")
+	flags.BoolVar(&config.AllowTrailingComment, "allow-trailing-comment", false, "Allow blocks to end with a comment")
+	flags.BoolVar(&config.ForceCuddleErrCheckAndAssign, "force-err-cuddling", false, "Force cuddling of error checks with error var assignment")
+	flags.BoolVar(&config.ForceExclusiveShortDeclarations, "force-short-decl-cuddling", false, "Force short declarations to cuddle by themselves")
+	flags.BoolVar(&config.StrictAppend, "strict-append", true, "Strict rules for append")
+	flags.IntVar(&config.ForceCaseTrailingWhitespaceLimit, "force-case-trailing-whitespace", 0, "Force newlines for case blocks > this number.")
+
+	flags.String("allow-cuddle-with-calls", "Lock,RLock", "Comma separated list of idents that can have cuddles after")
+	flags.String("allow-cuddle-with-rhs", "Unlock,RUnlock", "Comma separated list of idents that can have cuddles before")
+	flags.String("error-variable-names", "err", "Comma separated list of error variable names")
+
+	return *flags
+}
+
+func lookupAndSplit(pass *analysis.Pass, flagName string) []string {
+	flagVal := pass.Analyzer.Flags.Lookup(flagName)
+	if flagVal == nil {
+		return nil
+	}
+
+	return strings.Split(flagVal.Value.String(), ",")
+}
+
+func run(pass *analysis.Pass) (interface{}, error) {
+	if v := lookupAndSplit(pass, "allow-cuddle-with-calls"); v != nil {
+		config.AllowCuddleWithCalls = v
+	}
+
+	if v := lookupAndSplit(pass, "allow-cuddle-with-rhs"); v != nil {
+		config.AllowCuddleWithRHS = v
+	}
+
+	if v := lookupAndSplit(pass, "error-variable-names"); v != nil {
+		config.ErrorVariableNames = v
+	}
+
+	for _, file := range pass.Files {
+		filename := pass.Fset.Position(file.Pos()).Filename
+		if !strings.HasSuffix(filename, ".go") {
+			continue
+		}
+
+		processor := newProcessorWithConfig(file, pass.Fset, &config)
+		processor.parseAST()
+
+		for pos, fix := range processor.result {
+			textEdits := []analysis.TextEdit{}
+			for _, f := range fix.fixRanges {
+				textEdits = append(textEdits, analysis.TextEdit{
+					Pos:     f.fixRangeStart,
+					End:     f.fixRangeEnd,
+					NewText: []byte("\n"),
+				})
+			}
+
+			pass.Report(analysis.Diagnostic{
+				Pos:      pos,
+				Category: "whitespace",
+				Message:  fix.reason,
+				SuggestedFixes: []analysis.SuggestedFix{
+					{
+						TextEdits: textEdits,
+					},
+				},
+			})
+		}
+	}
+
+	//nolint:nilnil // A pass don't need to return anything.
+	return nil, nil
+}
diff --git a/cmd/wsl/main.go b/cmd/wsl/main.go
index a459b2e..672e4d5 100644
--- a/cmd/wsl/main.go
+++ b/cmd/wsl/main.go
@@ -1,133 +1,10 @@
 package main
 
 import (
-	"flag"
-	"fmt"
-	"os"
-	"path/filepath"
-	"strings"
-
 	"github.com/bombsimon/wsl/v3"
+	"golang.org/x/tools/go/analysis/singlechecker"
 )
 
 func main() {
-	var (
-		args         []string
-		help         bool
-		noTest       bool
-		showWarnings bool
-		cwd, _       = os.Getwd()
-		files        = []string{}
-		finalFiles   = []string{}
-		config       = wsl.DefaultConfig()
-	)
-
-	flag.BoolVar(&help, "h", false, "Show this help text")
-	flag.BoolVar(&help, "help", false, "")
-	flag.BoolVar(&noTest, "n", false, "Don't lint test files")
-	flag.BoolVar(&noTest, "no-test", false, "")
-	flag.BoolVar(&showWarnings, "w", false, "Show warnings (ignored rules)")
-	flag.BoolVar(&showWarnings, "warnings", false, "")
-
-	flag.BoolVar(&config.StrictAppend, "strict-append", true, "Strict rules for append")
-	flag.BoolVar(&config.AllowAssignAndCallCuddle, "allow-assign-and-call", true, "Allow assignments and calls to be cuddled (if using same variable/type)")
-	flag.BoolVar(&config.AllowAssignAndAnythingCuddle, "allow-assign-and-anything", false, "Allow assignments and anything to be cuddled")
-	flag.BoolVar(&config.AllowMultiLineAssignCuddle, "allow-multi-line-assign", true, "Allow cuddling with multi line assignments")
-	flag.BoolVar(&config.AllowCuddleDeclaration, "allow-cuddle-declarations", false, "Allow declarations to be cuddled")
-	flag.BoolVar(&config.AllowTrailingComment, "allow-trailing-comment", false, "Allow blocks to end with a comment")
-	flag.BoolVar(&config.AllowSeparatedLeadingComment, "allow-separated-leading-comment", false, "Allow empty newlines in leading comments")
-	flag.BoolVar(&config.ForceCuddleErrCheckAndAssign, "force-err-cuddling", false, "Force cuddling of error checks with error var assignment")
-	flag.BoolVar(&config.ForceExclusiveShortDeclarations, "force-short-decl-cuddling", false, "Force short declarations to cuddle by themselves")
-	flag.IntVar(&config.ForceCaseTrailingWhitespaceLimit, "force-case-trailing-whitespace", 0, "Force newlines for case blocks > this number.")
-
-	flag.Parse()
-
-	if help {
-		showHelp()
-		return
-	}
-
-	args = flag.Args()
-	if len(args) == 0 {
-		args = []string{"./..."}
-	}
-
-	for _, f := range args {
-		if strings.HasSuffix(f, "/...") {
-			dir, _ := filepath.Split(f)
-
-			files = append(files, expandGoWildcard(dir)...)
-
-			continue
-		}
-
-		if _, err := os.Stat(f); err == nil {
-			files = append(files, f)
-		}
-	}
-
-	// Use relative path to print shorter names, sort out test files if chosen.
-	for _, f := range files {
-		if noTest {
-			if strings.HasSuffix(f, "_test.go") {
-				continue
-			}
-		}
-
-		if relativePath, err := filepath.Rel(cwd, f); err == nil {
-			finalFiles = append(finalFiles, relativePath)
-
-			continue
-		}
-
-		finalFiles = append(finalFiles, f)
-	}
-
-	processor := wsl.NewProcessorWithConfig(config)
-	result, warnings := processor.ProcessFiles(finalFiles)
-
-	for _, r := range result {
-		fmt.Println(r.String())
-	}
-
-	if showWarnings && len(warnings) > 0 {
-		fmt.Println()
-		fmt.Println("⚠️  Warnings found")
-
-		for _, w := range warnings {
-			fmt.Println(w)
-		}
-	}
-
-	if len(result) > 0 {
-		os.Exit(2)
-	}
-}
-
-func expandGoWildcard(root string) []string {
-	foundFiles := []string{}
-
-	_ = filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
-		// Only append go files
-		if !strings.HasSuffix(info.Name(), ".go") {
-			return nil
-		}
-
-		foundFiles = append(foundFiles, path)
-
-		return nil
-	})
-
-	return foundFiles
-}
-
-func showHelp() {
-	helpText := `Usage: wsl <file> [files...]
-
-Also supports package syntax but will use it in relative path, i.e. ./pkg/...
-
-Flags:`
-
-	fmt.Println(helpText)
-	flag.PrintDefaults()
+	singlechecker.Main(wsl.Analyzer)
 }
diff --git a/doc/configuration.md b/doc/configuration.md
index 5e9aea3..70217d1 100644
--- a/doc/configuration.md
+++ b/doc/configuration.md
@@ -159,7 +159,7 @@ func example() string {
 }
 ```
 
-become legal, as the whitespace _after_ (or between) each comment block 
+become legal, as the whitespace _after_ (or between) each comment block
 doesn't count against whitespace before the first actual statement.
 
 ### [force-case-trailing-whitespace](rules.md#case-block-should-end-with-newline-at-this-size)
@@ -233,42 +233,6 @@ var (
 )
 ```
 
-### [allow-case-trailing-whitespace](rules.md#block-should-not-end-with-a-whitespace-or-comment)
-
-Controls if you may end case statements with a whitespace. This option is
-independent of other blocks and was introduced to improve readability for
-complex blocks.
-
-> Default value: false
-
-Supported when true:
-
-```go
-switch {
-case 1:
-    fmt.Println("x")
-
-case 2:
-    fmt.Println("x")
-
-case 3:
-    fmt.Pritnln("x")
-}
-```
-
-Required when false:
-
-```go
-switch {
-case 1:
-    fmt.Println("x")
-case 2:
-    fmt.Println("x")
-case 3:
-    fmt.Pritnln("x")
-}
-```
-
 ### [allow-trailing-comment](rules.md#block-should-not-end-with-a-whitespace-or-comment)
 
 Controls if blocks can end with comments. This is not encouraged sine it's
@@ -361,5 +325,5 @@ if err != nil {
 }
 ```
 
-**Note**: this means the option _overrides_ the 
+**Note**: this means the option _overrides_ the
 [force-err-cuddling](#force-err-cuddling) option above, among others.
diff --git a/go.mod b/go.mod
index cc9227c..5f24579 100644
--- a/go.mod
+++ b/go.mod
@@ -2,14 +2,9 @@ module github.com/bombsimon/wsl/v3
 
 go 1.19
 
-require github.com/stretchr/testify v1.8.1
+require golang.org/x/tools v0.6.0
 
 require (
-	github.com/davecgh/go-spew v1.1.1 // indirect
-	github.com/kr/text v0.2.0 // indirect
-	github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
-	github.com/pmezard/go-difflib v1.0.0 // indirect
-	gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
-	gopkg.in/yaml.v2 v2.4.0 // indirect
-	gopkg.in/yaml.v3 v3.0.1 // indirect
+	golang.org/x/mod v0.8.0 // indirect
+	golang.org/x/sys v0.5.0 // indirect
 )
diff --git a/go.sum b/go.sum
index bd47350..44d387e 100644
--- a/go.sum
+++ b/go.sum
@@ -1,36 +1,7 @@
-github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
-github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
-github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
-github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
-github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
-github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
-github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
-github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
-github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
-github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
-github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
-github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
-github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
-github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
-github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
-github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
-gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
-gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
-gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
-gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
-gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
+golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
diff --git a/wsl.go b/wsl.go
index 1b139c0..636e35b 100644
--- a/wsl.go
+++ b/wsl.go
@@ -3,45 +3,44 @@ package wsl
 import (
 	"fmt"
 	"go/ast"
-	"go/parser"
 	"go/token"
-	"os"
 	"reflect"
+	"sort"
 	"strings"
 )
 
 // Error reason strings.
 const (
-	reasonMustCuddleErrCheck             = "if statements that check an error must be cuddled with the statement that assigned the error"
-	reasonOnlyCuddleIfWithAssign         = "if statements should only be cuddled with assignments"
-	reasonOnlyOneCuddle                  = "only one cuddle assignment allowed before if statement"
-	reasonOnlyCuddleWithUsedAssign       = "if statements should only be cuddled with assignments used in the if statement itself"
-	reasonOnlyCuddle2LineReturn          = "return statements should not be cuddled if block has more than two lines"
-	reasonMultiLineBranchCuddle          = "branch statements should not be cuddled if block has more than two lines"
+	reasonAnonSwitchCuddled              = "anonymous switch statements should never be cuddled"
 	reasonAppendCuddledWithoutUse        = "append only allowed to cuddle with appended value"
 	reasonAssignsCuddleAssign            = "assignments should only be cuddled with other assignments"
-	reasonNeverCuddleDeclare             = "declarations should never be cuddled"
-	reasonExpressionCuddledWithDeclOrRet = "expressions should not be cuddled with declarations or returns"
-	reasonExpressionCuddledWithBlock     = "expressions should not be cuddled with blocks"
-	reasonExprCuddlingNonAssignedVar     = "only cuddled expressions if assigning variable or using from line above"
-	reasonOneCuddleBeforeRange           = "only one cuddle assignment allowed before range statement"
-	reasonRangeCuddledWithoutUse         = "ranges should only be cuddled with assignments used in the iteration"
-	reasonOneCuddleBeforeDefer           = "only one cuddle assignment allowed before defer statement"
+	reasonBlockEndsWithWS                = "block should not end with a whitespace (or comment)"
+	reasonBlockStartsWithWS              = "block should not start with a whitespace"
+	reasonCaseBlockTooCuddly             = "case block should end with newline at this size"
 	reasonDeferCuddledWithOtherVar       = "defer statements should only be cuddled with expressions on same variable"
-	reasonForWithoutCondition            = "for statement without condition should never be cuddled"
-	reasonForWithMoreThanOneCuddle       = "only one cuddle assignment allowed before for statement"
+	reasonExprCuddlingNonAssignedVar     = "only cuddled expressions if assigning variable or using from line above"
+	reasonExpressionCuddledWithBlock     = "expressions should not be cuddled with blocks"
+	reasonExpressionCuddledWithDeclOrRet = "expressions should not be cuddled with declarations or returns"
 	reasonForCuddledAssignWithoutUse     = "for statements should only be cuddled with assignments used in the iteration"
-	reasonOneCuddleBeforeGo              = "only one cuddle assignment allowed before go statement"
+	reasonForWithoutCondition            = "for statement without condition should never be cuddled"
 	reasonGoFuncWithoutAssign            = "go statements can only invoke functions assigned on line above"
-	reasonSwitchManyCuddles              = "only one cuddle assignment allowed before switch statement"
-	reasonAnonSwitchCuddled              = "anonymous switch statements should never be cuddled"
+	reasonMultiLineBranchCuddle          = "branch statements should not be cuddled if block has more than two lines"
+	reasonMustCuddleErrCheck             = "if statements that check an error must be cuddled with the statement that assigned the error"
+	reasonNeverCuddleDeclare             = "declarations should never be cuddled"
+	reasonOnlyCuddle2LineReturn          = "return statements should not be cuddled if block has more than two lines"
+	reasonOnlyCuddleIfWithAssign         = "if statements should only be cuddled with assignments"
+	reasonOnlyCuddleWithUsedAssign       = "if statements should only be cuddled with assignments used in the if statement itself"
+	reasonOnlyOneCuddleBeforeDefer       = "only one cuddle assignment allowed before defer statement"
+	reasonOnlyOneCuddleBeforeFor         = "only one cuddle assignment allowed before for statement"
+	reasonOnlyOneCuddleBeforeGo          = "only one cuddle assignment allowed before go statement"
+	reasonOnlyOneCuddleBeforeIf          = "only one cuddle assignment allowed before if statement"
+	reasonOnlyOneCuddleBeforeRange       = "only one cuddle assignment allowed before range statement"
+	reasonOnlyOneCuddleBeforeSwitch      = "only one cuddle assignment allowed before switch statement"
+	reasonOnlyOneCuddleBeforeTypeSwitch  = "only one cuddle assignment allowed before type switch statement"
+	reasonRangeCuddledWithoutUse         = "ranges should only be cuddled with assignments used in the iteration"
+	reasonShortDeclNotExclusive          = "short declaration should cuddle only with other short declarations"
 	reasonSwitchCuddledWithoutUse        = "switch statements should only be cuddled with variables switched"
-	reasonTypeSwitchTooCuddled           = "only one cuddle assignment allowed before type switch statement"
 	reasonTypeSwitchCuddledWithoutUse    = "type switch statements should only be cuddled with variables switched"
-	reasonBlockStartsWithWS              = "block should not start with a whitespace"
-	reasonBlockEndsWithWS                = "block should not end with a whitespace (or comment)"
-	reasonCaseBlockTooCuddly             = "case block should end with newline at this size"
-	reasonShortDeclNotExclusive          = "short declaration should cuddle only with other short declarations"
 )
 
 // Warning strings.
@@ -54,6 +53,7 @@ const (
 	warnUnknownRHS                 = "UNKNOWN RHS"
 )
 
+// Configuration represents configurable settingds for the linter.
 type Configuration struct {
 	// StrictAppend will do strict checking when assigning from append (x =
 	// append(x, y)). If this is set to true the append call must append either
@@ -82,7 +82,7 @@ type Configuration struct {
 	//  x = AnotherAssign()
 	AllowAssignAndCallCuddle bool
 
-	// AllowAssignAndCallCuddle allows assignments to be cuddled with anything.
+	// AllowAssignAndAnythingCuddle allows assignments to be cuddled with anything.
 	// Example supported with this set to true:
 	//  if x == 1 {
 	//  	x = 0
@@ -176,94 +176,40 @@ type Configuration struct {
 	ForceExclusiveShortDeclarations bool
 }
 
-// DefaultConfig returns default configuration.
-func DefaultConfig() Configuration {
-	return Configuration{
-		StrictAppend:                     true,
-		AllowAssignAndCallCuddle:         true,
-		AllowAssignAndAnythingCuddle:     false,
-		AllowMultiLineAssignCuddle:       true,
-		AllowTrailingComment:             false,
-		AllowSeparatedLeadingComment:     false,
-		ForceCuddleErrCheckAndAssign:     false,
-		ForceExclusiveShortDeclarations:  false,
-		ForceCaseTrailingWhitespaceLimit: 0,
-		AllowCuddleWithCalls:             []string{"Lock", "RLock"},
-		AllowCuddleWithRHS:               []string{"Unlock", "RUnlock"},
-		ErrorVariableNames:               []string{"err"},
-	}
-}
-
-// Result represents the result of one error.
-type Result struct {
-	FileName   string
-	LineNumber int
-	Position   token.Position
-	Reason     string
+// fix is a range to fixup.
+type fix struct {
+	fixRangeStart token.Pos
+	fixRangeEnd   token.Pos
 }
 
-// String returns the filename, line number and reason of a Result.
-func (r *Result) String() string {
-	return fmt.Sprintf("%s:%d: %s", r.FileName, r.LineNumber, r.Reason)
+// result represents the result of one error.
+type result struct {
+	fixRanges []fix
+	reason    string
 }
 
-type Processor struct {
-	config   Configuration
-	result   []Result
-	warnings []string
-	fileSet  *token.FileSet
+// processor is the type that keeps track of the file and fileset and holds the
+// results from parsing the AST.
+type processor struct {
+	config   *Configuration
 	file     *ast.File
+	fileSet  *token.FileSet
+	result   map[token.Pos]result
+	warnings []string
 }
 
-// NewProcessor will create a Processor.
-//
-//nolint:gocritic // It's fine to copy config struct
-func NewProcessorWithConfig(cfg Configuration) *Processor {
-	return &Processor{
-		result: []Result{},
-		config: cfg,
-	}
-}
-
-// NewProcessor will create a Processor.
-func NewProcessor() *Processor {
-	return NewProcessorWithConfig(DefaultConfig())
-}
-
-// ProcessFiles takes a string slice with file names (full paths) and lints
-// them.
-//
-//nolint:gocritic // Don't want named returns
-func (p *Processor) ProcessFiles(filenames []string) ([]Result, []string) {
-	for _, filename := range filenames {
-		data, err := os.ReadFile(filename)
-		if err != nil {
-			panic(err)
-		}
-
-		p.process(filename, data)
+// newProcessorWithConfig will create a Processor with the passed configuration.
+func newProcessorWithConfig(file *ast.File, fileSet *token.FileSet, cfg *Configuration) *processor {
+	return &processor{
+		config:  cfg,
+		file:    file,
+		fileSet: fileSet,
+		result:  make(map[token.Pos]result),
 	}
-
-	return p.result, p.warnings
 }
 
-func (p *Processor) process(filename string, data []byte) {
-	fileSet := token.NewFileSet()
-	file, err := parser.ParseFile(fileSet, filename, data, parser.ParseComments)
-	// If the file is not parsable let's add a syntax error and move on.
-	if err != nil {
-		p.result = append(p.result, Result{
-			FileName:   filename,
-			LineNumber: 0,
-			Reason:     fmt.Sprintf("invalid syntax, file cannot be linted (%s)", err.Error()),
-		})
-
-		return
-	}
-
-	p.fileSet = fileSet
-	p.file = file
-
+// parseAST will parse the AST attached to the Processor instance.
+func (p *processor) parseAST() {
 	for _, d := range p.file.Decls {
 		switch v := d.(type) {
 		case *ast.FuncDecl:
@@ -279,7 +225,7 @@ func (p *Processor) process(filename string, data []byte) {
 
 // parseBlockBody will parse any kind of block statements such as switch cases
 // and if statements. A list of Result is returned.
-func (p *Processor) parseBlockBody(ident *ast.Ident, block *ast.BlockStmt) {
+func (p *processor) parseBlockBody(ident *ast.Ident, block *ast.BlockStmt) {
 	// Nothing to do if there's no value.
 	if reflect.ValueOf(block).IsNil() {
 		return
@@ -294,7 +240,7 @@ func (p *Processor) parseBlockBody(ident *ast.Ident, block *ast.BlockStmt) {
 
 // parseBlockStatements will parse all the statements found in the body of a
 // node. A list of Result is returned.
-func (p *Processor) parseBlockStatements(statements []ast.Stmt) {
+func (p *processor) parseBlockStatements(statements []ast.Stmt) {
 	for i, stmt := range statements {
 		// 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
@@ -413,9 +359,30 @@ func (p *Processor) parseBlockStatements(statements []ast.Stmt) {
 		// it was and use *that* statement's position
 		if p.config.ForceExclusiveShortDeclarations && cuddledWithLastStmt {
 			if p.isShortDecl(stmt) && !p.isShortDecl(previousStatement) {
-				p.addError(stmt.Pos(), reasonShortDeclNotExclusive)
+				var reportNode ast.Node = previousStatement
+
+				cm := ast.NewCommentMap(p.fileSet, stmt, p.file.Comments)
+				if cg, ok := cm[stmt]; ok && len(cg) > 0 {
+					for _, c := range cg {
+						if c.Pos() > previousStatement.End() && c.End() < stmt.Pos() {
+							reportNode = c
+						}
+					}
+				}
+
+				p.addErrorRange(
+					stmt.Pos(),
+					reportNode.End(),
+					reportNode.End(),
+					reasonShortDeclNotExclusive,
+				)
 			} else if p.isShortDecl(previousStatement) && !p.isShortDecl(stmt) {
-				p.addError(previousStatement.Pos(), reasonShortDeclNotExclusive)
+				p.addErrorRange(
+					previousStatement.Pos(),
+					stmt.Pos(),
+					stmt.Pos(),
+					reasonShortDeclNotExclusive,
+				)
 			}
 		}
 
@@ -428,6 +395,31 @@ func (p *Processor) parseBlockStatements(statements []ast.Stmt) {
 			}
 		}
 
+		reportNewlineTwoLinesAbove := func(n1, n2 ast.Node, reason string) {
+			if atLeastOneInListsMatch(rightAndLeftHandSide, assignedOnLineAbove) ||
+				atLeastOneInListsMatch(assignedOnLineAbove, calledOrAssignedFirstInBlock) {
+				// If both the assignment on the line above _and_ the assignment
+				// two lines above is part of line or first in block, add the
+				// newline as if non were.
+				_, isAssignmentTwoLinesAbove := statements[i-2].(*ast.AssignStmt)
+				assignedTwoLinesAbove := p.findLHS(statements[i-2])
+
+				if isAssignmentTwoLinesAbove &&
+					(atLeastOneInListsMatch(rightAndLeftHandSide, assignedTwoLinesAbove) ||
+						atLeastOneInListsMatch(assignedTwoLinesAbove, calledOrAssignedFirstInBlock)) {
+					p.addWhitespaceBeforeError(n1, reason)
+				} else {
+					// If the variable on the line above is allowed to be
+					// cuddled, break two lines above so we keep the proper
+					// cuddling.
+					p.addErrorRange(n1.Pos(), n2.Pos(), n2.Pos(), reason)
+				}
+			} else {
+				// If not, break here so we separate the cuddled variable.
+				p.addWhitespaceBeforeError(n1, reason)
+			}
+		}
+
 		switch t := stmt.(type) {
 		case *ast.IfStmt:
 			checkingErrInitializedInline := func() bool {
@@ -460,7 +452,12 @@ func (p *Processor) parseBlockStatements(statements []ast.Stmt) {
 					}
 
 					if atLeastOneInListsMatch(assignedOnLineAbove, p.config.ErrorVariableNames) {
-						p.addError(t.Pos(), reasonMustCuddleErrCheck)
+						p.addErrorRange(
+							stmt.Pos(),
+							previousStatement.End(),
+							stmt.Pos(),
+							reasonMustCuddleErrCheck,
+						)
 					}
 				}
 
@@ -468,12 +465,12 @@ func (p *Processor) parseBlockStatements(statements []ast.Stmt) {
 			}
 
 			if len(assignedOnLineAbove) == 0 {
-				p.addError(t.Pos(), reasonOnlyCuddleIfWithAssign)
+				p.addWhitespaceBeforeError(t, reasonOnlyCuddleIfWithAssign)
 				continue
 			}
 
 			if moreThanOneStatementAbove() {
-				p.addError(t.Pos(), reasonOnlyOneCuddle)
+				reportNewlineTwoLinesAbove(t, statements[i-1], reasonOnlyOneCuddleBeforeIf)
 				continue
 			}
 
@@ -485,26 +482,26 @@ func (p *Processor) parseBlockStatements(statements []ast.Stmt) {
 				continue
 			}
 
-			p.addError(t.Pos(), reasonOnlyCuddleWithUsedAssign)
+			p.addWhitespaceBeforeError(t, reasonOnlyCuddleWithUsedAssign)
 		case *ast.ReturnStmt:
 			if isLastStatementInBlockOfOnlyTwoLines() {
 				continue
 			}
 
-			p.addError(t.Pos(), reasonOnlyCuddle2LineReturn)
+			p.addWhitespaceBeforeError(t, reasonOnlyCuddle2LineReturn)
 		case *ast.BranchStmt:
 			if isLastStatementInBlockOfOnlyTwoLines() {
 				continue
 			}
 
-			p.addError(t.Pos(), reasonMultiLineBranchCuddle)
+			p.addWhitespaceBeforeError(t, reasonMultiLineBranchCuddle)
 		case *ast.AssignStmt:
 			// append is usually an assignment but should not be allowed to be
 			// cuddled with anything not appended.
 			if len(rightHandSide) > 0 && rightHandSide[len(rightHandSide)-1] == "append" {
 				if p.config.StrictAppend {
 					if !atLeastOneInListsMatch(calledOrAssignedOnLineAbove, rightHandSide) {
-						p.addError(t.Pos(), reasonAppendCuddledWithoutUse)
+						p.addWhitespaceBeforeError(t, reasonAppendCuddledWithoutUse)
 					}
 				}
 
@@ -535,10 +532,10 @@ func (p *Processor) parseBlockStatements(statements []ast.Stmt) {
 				}
 			}
 
-			p.addError(t.Pos(), reasonAssignsCuddleAssign)
+			p.addWhitespaceBeforeError(t, reasonAssignsCuddleAssign)
 		case *ast.DeclStmt:
 			if !p.config.AllowCuddleDeclaration {
-				p.addError(t.Pos(), reasonNeverCuddleDeclare)
+				p.addWhitespaceBeforeError(t, reasonNeverCuddleDeclare)
 			}
 		case *ast.ExprStmt:
 			switch previousStatement.(type) {
@@ -547,9 +544,9 @@ func (p *Processor) parseBlockStatements(statements []ast.Stmt) {
 					continue
 				}
 
-				p.addError(t.Pos(), reasonExpressionCuddledWithDeclOrRet)
+				p.addWhitespaceBeforeError(t, reasonExpressionCuddledWithDeclOrRet)
 			case *ast.IfStmt, *ast.RangeStmt, *ast.SwitchStmt:
-				p.addError(t.Pos(), reasonExpressionCuddledWithBlock)
+				p.addWhitespaceBeforeError(t, reasonExpressionCuddledWithBlock)
 			}
 
 			// If the expression is called on a type or variable used or
@@ -567,17 +564,17 @@ func (p *Processor) parseBlockStatements(statements []ast.Stmt) {
 			// If we assigned variables on the line above but didn't use them in
 			// this expression there should probably be a newline between them.
 			if len(assignedOnLineAbove) > 0 && !atLeastOneInListsMatch(rightAndLeftHandSide, assignedOnLineAbove) {
-				p.addError(t.Pos(), reasonExprCuddlingNonAssignedVar)
+				p.addWhitespaceBeforeError(t, reasonExprCuddlingNonAssignedVar)
 			}
 		case *ast.RangeStmt:
 			if moreThanOneStatementAbove() {
-				p.addError(t.Pos(), reasonOneCuddleBeforeRange)
+				reportNewlineTwoLinesAbove(t, statements[i-1], reasonOnlyOneCuddleBeforeRange)
 				continue
 			}
 
 			if !atLeastOneInListsMatch(rightAndLeftHandSide, assignedOnLineAbove) {
 				if !atLeastOneInListsMatch(assignedOnLineAbove, calledOrAssignedFirstInBlock) {
-					p.addError(t.Pos(), reasonRangeCuddledWithoutUse)
+					p.addWhitespaceBeforeError(t, reasonRangeCuddledWithoutUse)
 				}
 			}
 		case *ast.DeferStmt:
@@ -606,8 +603,7 @@ func (p *Processor) parseBlockStatements(statements []ast.Stmt) {
 			}
 
 			if moreThanOneStatementAbove() {
-				p.addError(t.Pos(), reasonOneCuddleBeforeDefer)
-
+				reportNewlineTwoLinesAbove(t, statements[i-1], reasonOnlyOneCuddleBeforeDefer)
 				continue
 			}
 
@@ -620,7 +616,7 @@ func (p *Processor) parseBlockStatements(statements []ast.Stmt) {
 			}
 
 			// Allow use to cuddled defer func literals with usages on line
-			// abouve. Example:
+			// above. Example:
 			// b := getB()
 			// defer func() {
 			//     makesSenseToUse(b)
@@ -638,18 +634,16 @@ func (p *Processor) parseBlockStatements(statements []ast.Stmt) {
 			}
 
 			if !atLeastOneInListsMatch(rightAndLeftHandSide, assignedOnLineAbove) {
-				p.addError(t.Pos(), reasonDeferCuddledWithOtherVar)
+				p.addWhitespaceBeforeError(t, reasonDeferCuddledWithOtherVar)
 			}
 		case *ast.ForStmt:
 			if len(rightAndLeftHandSide) == 0 {
-				p.addError(t.Pos(), reasonForWithoutCondition)
-
+				p.addWhitespaceBeforeError(t, reasonForWithoutCondition)
 				continue
 			}
 
 			if moreThanOneStatementAbove() {
-				p.addError(t.Pos(), reasonForWithMoreThanOneCuddle)
-
+				reportNewlineTwoLinesAbove(t, statements[i-1], reasonOnlyOneCuddleBeforeFor)
 				continue
 			}
 
@@ -658,7 +652,7 @@ func (p *Processor) parseBlockStatements(statements []ast.Stmt) {
 			// first line in the block for details.
 			if !atLeastOneInListsMatch(rightAndLeftHandSide, assignedOnLineAbove) {
 				if !atLeastOneInListsMatch(assignedOnLineAbove, calledOrAssignedFirstInBlock) {
-					p.addError(t.Pos(), reasonForCuddledAssignWithoutUse)
+					p.addWhitespaceBeforeError(t, reasonForCuddledAssignWithoutUse)
 				}
 			}
 		case *ast.GoStmt:
@@ -667,8 +661,7 @@ func (p *Processor) parseBlockStatements(statements []ast.Stmt) {
 			}
 
 			if moreThanOneStatementAbove() {
-				p.addError(t.Pos(), reasonOneCuddleBeforeGo)
-
+				reportNewlineTwoLinesAbove(t, statements[i-1], reasonOnlyOneCuddleBeforeGo)
 				continue
 			}
 
@@ -689,26 +682,24 @@ func (p *Processor) parseBlockStatements(statements []ast.Stmt) {
 			}
 
 			if !atLeastOneInListsMatch(rightAndLeftHandSide, assignedOnLineAbove) {
-				p.addError(t.Pos(), reasonGoFuncWithoutAssign)
+				p.addWhitespaceBeforeError(t, reasonGoFuncWithoutAssign)
 			}
 		case *ast.SwitchStmt:
 			if moreThanOneStatementAbove() {
-				p.addError(t.Pos(), reasonSwitchManyCuddles)
-
+				reportNewlineTwoLinesAbove(t, statements[i-1], reasonOnlyOneCuddleBeforeSwitch)
 				continue
 			}
 
 			if !atLeastOneInListsMatch(rightAndLeftHandSide, assignedOnLineAbove) {
 				if len(rightAndLeftHandSide) == 0 {
-					p.addError(t.Pos(), reasonAnonSwitchCuddled)
+					p.addWhitespaceBeforeError(t, reasonAnonSwitchCuddled)
 				} else {
-					p.addError(t.Pos(), reasonSwitchCuddledWithoutUse)
+					p.addWhitespaceBeforeError(t, reasonSwitchCuddledWithoutUse)
 				}
 			}
 		case *ast.TypeSwitchStmt:
 			if moreThanOneStatementAbove() {
-				p.addError(t.Pos(), reasonTypeSwitchTooCuddled)
-
+				reportNewlineTwoLinesAbove(t, statements[i-1], reasonOnlyOneCuddleBeforeTypeSwitch)
 				continue
 			}
 
@@ -717,7 +708,7 @@ func (p *Processor) parseBlockStatements(statements []ast.Stmt) {
 				// Allow type assertion on variables used in the first case
 				// immediately.
 				if !atLeastOneInListsMatch(assignedOnLineAbove, calledOrAssignedFirstInBlock) {
-					p.addError(t.Pos(), reasonTypeSwitchCuddledWithoutUse)
+					p.addWhitespaceBeforeError(t, reasonTypeSwitchCuddledWithoutUse)
 				}
 			}
 		case *ast.CaseClause, *ast.CommClause:
@@ -735,7 +726,7 @@ func (p *Processor) parseBlockStatements(statements []ast.Stmt) {
 // directly as the first argument inside a body.
 // The body will then be parsed as a *ast.BlockStmt (regular block) or as a list
 // of []ast.Stmt (case block).
-func (p *Processor) firstBodyStatement(i int, allStmt []ast.Stmt) ast.Node {
+func (p *processor) firstBodyStatement(i int, allStmt []ast.Stmt) ast.Node {
 	stmt := allStmt[i]
 
 	// Start by checking if the statement has a body (probably if-statement,
@@ -791,7 +782,7 @@ func (p *Processor) firstBodyStatement(i int, allStmt []ast.Stmt) ast.Node {
 	return firstBodyStatement
 }
 
-func (p *Processor) findLHS(node ast.Node) []string {
+func (p *processor) findLHS(node ast.Node) []string {
 	var lhs []string
 
 	if node == nil {
@@ -850,7 +841,7 @@ func (p *Processor) findLHS(node ast.Node) []string {
 	return lhs
 }
 
-func (p *Processor) findRHS(node ast.Node) []string {
+func (p *processor) findRHS(node ast.Node) []string {
 	var rhs []string
 
 	if node == nil {
@@ -934,7 +925,7 @@ func (p *Processor) findRHS(node ast.Node) []string {
 	return rhs
 }
 
-func (p *Processor) isShortDecl(node ast.Node) bool {
+func (p *processor) isShortDecl(node ast.Node) bool {
 	if t, ok := node.(*ast.AssignStmt); ok {
 		return t.Tok == token.DEFINE
 	}
@@ -942,7 +933,7 @@ func (p *Processor) isShortDecl(node ast.Node) bool {
 	return false
 }
 
-func (p *Processor) findBlockStmt(node ast.Node) []*ast.BlockStmt {
+func (p *processor) findBlockStmt(node ast.Node) []*ast.BlockStmt {
 	var blocks []*ast.BlockStmt
 
 	switch t := node.(type) {
@@ -1020,15 +1011,15 @@ func atLeastOneInListsMatch(listOne, listTwo []string) bool {
 // findLeadingAndTrailingWhitespaces will find leading and trailing whitespaces
 // in a node. The method takes comments in consideration which will make the
 // parser more gentle.
-func (p *Processor) findLeadingAndTrailingWhitespaces(ident *ast.Ident, stmt, nextStatement ast.Node) {
+func (p *processor) findLeadingAndTrailingWhitespaces(ident *ast.Ident, stmt, nextStatement ast.Node) {
 	var (
-		allowedLinesBeforeFirstStatement = 1
-		commentMap                       = ast.NewCommentMap(p.fileSet, stmt, p.file.Comments)
-		blockStatements                  []ast.Stmt
-		blockStartLine                   int
-		blockEndLine                     int
-		blockStartPos                    token.Pos
-		blockEndPos                      token.Pos
+		commentMap      = ast.NewCommentMap(p.fileSet, stmt, p.file.Comments)
+		blockStatements []ast.Stmt
+		blockStartLine  int
+		blockEndLine    int
+		blockStartPos   token.Pos
+		blockEndPos     token.Pos
+		isCase          bool
 	)
 
 	// Depending on the block type, get the statements in the block and where
@@ -1041,9 +1032,11 @@ func (p *Processor) findLeadingAndTrailingWhitespaces(ident *ast.Ident, stmt, ne
 	case *ast.CaseClause:
 		blockStatements = t.Body
 		blockStartPos = t.Colon
+		isCase = true
 	case *ast.CommClause:
 		blockStatements = t.Body
 		blockStartPos = t.Colon
+		isCase = true
 	default:
 		p.addWarning(warnWSNodeTypeNotImplemented, stmt.Pos(), stmt)
 
@@ -1064,152 +1057,238 @@ func (p *Processor) findLeadingAndTrailingWhitespaces(ident *ast.Ident, stmt, ne
 	}
 
 	var (
-		firstStatement    = blockStatements[0]
-		lastStatement     = blockStatements[len(blockStatements)-1]
-		seenCommentGroups = 0
+		firstStatement = blockStatements[0]
+		lastStatement  = blockStatements[len(blockStatements)-1]
 	)
 
 	// Get the comment related to the first statement, we do allow commends in
 	// the beginning of a block before the first statement.
-	if c, ok := commentMap[firstStatement]; ok {
-		for _, commentGroup := range c {
-			// If the comment group is on the same line as the block start
-			// (LBrace) we should not consider it.
-			if p.nodeStart(commentGroup) == blockStartLine {
+	var (
+		openingNodePos     = blockStartPos + 1
+		lastLeadingComment ast.Node
+	)
+
+	var (
+		firstStatementCommentGroups []*ast.CommentGroup
+		lastStatementCommentGroups  []*ast.CommentGroup
+	)
+
+	if cg, ok := commentMap[firstStatement]; ok && !isCase {
+		firstStatementCommentGroups = cg
+	} else {
+		// TODO: Just like with trailing whitespaces comments in a case block is
+		// tied to the last token of the first statement. For now we iterate over
+		// all comments in the stmt and grab those that's after colon and before
+		// first statement.
+		for _, cg := range commentMap {
+			if len(cg) < 1 {
 				continue
 			}
 
-			// We only care about comments before our statement from the comment
-			// map. As soon as we hit comments after our statement let's break
-			// out!
-			if commentGroup.Pos() > firstStatement.Pos() {
-				break
+			// If we have comments and the last comment ends before the first
+			// statement and the node is after the colon, this must be the node
+			// mapped to comments.
+			for _, c := range cg {
+				if c.End() < firstStatement.Pos() && c.Pos() > blockStartPos {
+					firstStatementCommentGroups = append(firstStatementCommentGroups, c)
+				}
 			}
 
-			// We store number of seen comment groups because we allow multiple
-			// groups with a newline between them; but if the first one has WS
-			// before it, we're not going to count it to force an error.
-			if p.config.AllowSeparatedLeadingComment {
-				cg := p.fileSet.Position(commentGroup.Pos()).Line
+			// And same if we have comments where the first comment is after the
+			// last statement but before the next statement (next case). As with
+			// the other things, if there is not next statement it's no next
+			// case and the logic will be handled when parsing the block.
+			if nextStatement == nil {
+				continue
+			}
 
-				if seenCommentGroups > 0 || cg == blockStartLine+1 {
-					seenCommentGroups++
+			for _, c := range cg {
+				if c.Pos() > lastStatement.End() && c.End() < nextStatement.Pos() {
+					lastStatementCommentGroups = append(lastStatementCommentGroups, c)
 				}
-			} else {
-				seenCommentGroups++
 			}
+		}
 
-			// Support both /* multiline */ and //single line comments
-			for _, c := range commentGroup.List {
-				allowedLinesBeforeFirstStatement += len(strings.Split(c.Text, "\n"))
+		// Since the comments come from a map they might not be ordered meaning
+		// that the last and first comment groups can be in the wrong order. We
+		// fix this by sorting all comments by pos after adding them all to the
+		// slice.
+		sort.Slice(firstStatementCommentGroups, func(i, j int) bool {
+			return firstStatementCommentGroups[i].Pos() < firstStatementCommentGroups[j].Pos()
+		})
+
+		sort.Slice(lastStatementCommentGroups, func(i, j int) bool {
+			return lastStatementCommentGroups[i].Pos() < lastStatementCommentGroups[j].Pos()
+		})
+	}
+
+	for _, commentGroup := range firstStatementCommentGroups {
+		// If the comment group is on the same line as the block start
+		// (LBrace) we should not consider it.
+		if p.nodeEnd(commentGroup) == blockStartLine {
+			openingNodePos = commentGroup.End()
+			continue
+		}
+
+		// We only care about comments before our statement from the comment
+		// map. As soon as we hit comments after our statement let's break
+		// out!
+		if commentGroup.Pos() > firstStatement.Pos() {
+			break
+		}
+
+		// We never allow leading whitespace for the first comment.
+		if lastLeadingComment == nil && p.nodeStart(commentGroup)-1 != blockStartLine {
+			p.addErrorRange(
+				openingNodePos,
+				openingNodePos,
+				commentGroup.Pos(),
+				reasonBlockStartsWithWS,
+			)
+		}
+
+		// If lastLeadingComment is set this is not the first comment so we
+		// should remove whitespace between them if we don't explicitly
+		// allow it.
+		if lastLeadingComment != nil && !p.config.AllowSeparatedLeadingComment {
+			if p.nodeStart(commentGroup)+1 != p.nodeEnd(lastLeadingComment) {
+				p.addErrorRange(
+					openingNodePos,
+					lastLeadingComment.End(),
+					commentGroup.Pos(),
+					reasonBlockStartsWithWS,
+				)
 			}
 		}
+
+		lastLeadingComment = commentGroup
 	}
 
-	// If we allow separated comments, allow for a space after each group
-	if p.config.AllowSeparatedLeadingComment {
-		if seenCommentGroups > 1 {
-			allowedLinesBeforeFirstStatement += seenCommentGroups - 1
-		} else if seenCommentGroups == 1 {
-			allowedLinesBeforeFirstStatement++
-		}
+	lastNodePos := openingNodePos
+	if lastLeadingComment != nil {
+		lastNodePos = lastLeadingComment.End()
+		blockStartLine = p.nodeEnd(lastLeadingComment)
 	}
 
-	// And now if the first statement is passed the number of allowed lines,
-	// then we had extra WS, possibly before the first comment group.
-	if p.nodeStart(firstStatement) > blockStartLine+allowedLinesBeforeFirstStatement {
-		p.addError(
-			blockStartPos,
+	// Check if we have a whitespace between the last node which can be the
+	// Lbrace, a comment on the same line or the last comment if we have
+	// comments inside the actual block and the first statement. This is never
+	// allowed.
+	if p.nodeStart(firstStatement)-1 != blockStartLine {
+		p.addErrorRange(
+			openingNodePos,
+			lastNodePos,
+			firstStatement.Pos(),
 			reasonBlockStartsWithWS,
 		)
 	}
 
 	// If the blockEndLine is not 0 we're a regular block (not case).
 	if blockEndLine != 0 {
-		if p.config.AllowTrailingComment {
-			if lastComment, ok := commentMap[lastStatement]; ok {
-				var (
-					lastCommentGroup = lastComment[len(lastComment)-1]
-					lastCommentLine  = lastCommentGroup.List[len(lastCommentGroup.List)-1]
-					countNewlines    = 0
-				)
+		// We don't want to reject example functions since they have to end with
+		// a comment.
+		if isExampleFunc(ident) {
+			return
+		}
+
+		var (
+			lastNode         ast.Node = lastStatement
+			trailingComments []ast.Node
+		)
 
-				countNewlines += len(strings.Split(lastCommentLine.Text, "\n"))
+		// Check if we have an comments _after_ the last statement and update
+		// the last node if so.
+		if c, ok := commentMap[lastStatement]; ok {
+			lastComment := c[len(c)-1]
+			if lastComment.Pos() > lastStatement.End() && lastComment.Pos() < stmt.End() {
+				lastNode = lastComment
+			}
+		}
 
-				// No newlines between trailing comments and end of block.
-				if p.nodeStart(lastCommentLine)+countNewlines != blockEndLine-1 {
-					return
+		// TODO: This should be improved.
+		// The trailing comments are mapped to the last statement item which can
+		// be anything depending on what the last statement is.
+		// In `fmt.Println("hello")`, trailing comments will be mapped to
+		// `*ast.BasicLit` for the "hello" string.
+		// A short term improvement can be to cache this but for now we naively
+		// iterate over all items when we check a block.
+		for _, commentGroups := range commentMap {
+			for _, commentGroup := range commentGroups {
+				if commentGroup.Pos() < lastNode.End() || commentGroup.End() > stmt.End() {
+					continue
 				}
+
+				trailingComments = append(trailingComments, commentGroup)
 			}
 		}
 
-		if p.nodeEnd(lastStatement) != blockEndLine-1 && !isExampleFunc(ident) {
-			p.addError(blockEndPos, reasonBlockEndsWithWS)
+		// TODO: Should this be relaxed?
+		// Given the old code we only allowed trailing newline if it was
+		// directly tied to the last statement so for backwards compatibility
+		// we'll do the same. This means we fail all but the last whitespace
+		// even when allowing trailing comments.
+		for _, comment := range trailingComments {
+			if p.nodeStart(comment)-p.nodeEnd(lastNode) > 1 {
+				p.addErrorRange(
+					blockEndPos,
+					lastNode.End(),
+					comment.Pos(),
+					reasonBlockEndsWithWS,
+				)
+			}
+
+			lastNode = comment
+		}
+
+		if !p.config.AllowTrailingComment && p.nodeEnd(stmt)-1 != p.nodeEnd(lastStatement) {
+			p.addErrorRange(
+				blockEndPos,
+				lastNode.End(),
+				stmt.End()-1,
+				reasonBlockEndsWithWS,
+			)
 		}
 
 		return
 	}
 
+	// Nothing to do if we're not looking for enforced newline.
+	if p.config.ForceCaseTrailingWhitespaceLimit == 0 {
+		return
+	}
+
 	// If we don't have any nextStatement the trailing whitespace will be
 	// handled when parsing the switch. If we do have a next statement we can
 	// see where it starts by getting it's colon position. We set the end of the
 	// current case to the position of the next case.
-	switch n := nextStatement.(type) {
-	case *ast.CaseClause:
-		blockEndPos = n.Case
-	case *ast.CommClause:
-		blockEndPos = n.Case
+	switch nextStatement.(type) {
+	case *ast.CaseClause, *ast.CommClause:
 	default:
 		// No more cases
 		return
 	}
 
-	blockEndLine = p.fileSet.Position(blockEndPos).Line - 1
-
-	var (
-		blockSize                = blockEndLine - blockStartLine
-		caseTrailingCommentLines int
-	)
-
-	// TODO: I don't know what comments are bound to in cases. For regular
-	// blocks the last comment is bound to the last statement but for cases
-	// they are bound to the case clause expression. This will however get us all
-	// comments and depending on the case expression this gets tricky.
-	//
-	// To handle this I get the comment map from the current statement (the case
-	// itself) and iterate through all groups and all comment within all groups.
-	// I then get the comments after the last statement but before the next case
-	// clause and just map each line of comment that way.
-	for _, commentGroups := range commentMap {
-		for _, commentGroup := range commentGroups {
-			for _, comment := range commentGroup.List {
-				commentLine := p.fileSet.Position(comment.Pos()).Line
-
-				// Ignore comments before the last statement.
-				if commentLine <= p.nodeStart(lastStatement) {
-					continue
-				}
-
-				// Ignore comments after the end of this case.
-				if commentLine > blockEndLine {
-					continue
-				}
-
-				// This allows /* multiline */ comments with newlines as well
-				// as regular (//) ones
-				caseTrailingCommentLines += len(strings.Split(comment.Text, "\n"))
-			}
-		}
+	var closingNode ast.Node = lastStatement
+	for _, commentGroup := range lastStatementCommentGroups {
+		// TODO: In future versions we might want to close the gaps between
+		// comments. However this is not currently reported in v3 so we
+		// won't add this for now.
+		// if p.nodeStart(commentGroup)-1 != p.nodeEnd(closingNode) {}
+		closingNode = commentGroup
 	}
 
-	hasTrailingWhitespace := p.nodeEnd(lastStatement)+caseTrailingCommentLines != blockEndLine
+	totalRowsInCase := p.nodeEnd(closingNode) - blockStartLine
+	if totalRowsInCase < p.config.ForceCaseTrailingWhitespaceLimit {
+		return
+	}
 
-	// If the force trailing limit is configured and we don't end with a newline.
-	if p.config.ForceCaseTrailingWhitespaceLimit > 0 && !hasTrailingWhitespace {
-		// Check if the block size is too big to miss the newline.
-		if blockSize >= p.config.ForceCaseTrailingWhitespaceLimit {
-			p.addError(lastStatement.Pos(), reasonCaseBlockTooCuddly)
-		}
+	if p.nodeEnd(closingNode)+1 == p.nodeStart(nextStatement) {
+		p.addErrorRange(
+			closingNode.Pos(),
+			closingNode.End(),
+			closingNode.End(),
+			reasonCaseBlockTooCuddly,
+		)
 	}
 }
 
@@ -1217,11 +1296,11 @@ func isExampleFunc(ident *ast.Ident) bool {
 	return ident != nil && strings.HasPrefix(ident.Name, "Example")
 }
 
-func (p *Processor) nodeStart(node ast.Node) int {
+func (p *processor) nodeStart(node ast.Node) int {
 	return p.fileSet.Position(node.Pos()).Line
 }
 
-func (p *Processor) nodeEnd(node ast.Node) int {
+func (p *processor) nodeEnd(node ast.Node) int {
 	line := p.fileSet.Position(node.End()).Line
 
 	if isEmptyLabeledStmt(node) {
@@ -1242,20 +1321,28 @@ func isEmptyLabeledStmt(node ast.Node) bool {
 	return empty
 }
 
-// Add an error for the file and line number for the current token.Pos with the
-// given reason.
-func (p *Processor) addError(pos token.Pos, reason string) {
-	position := p.fileSet.Position(pos)
+func (p *processor) addWhitespaceBeforeError(node ast.Node, reason string) {
+	p.addErrorRange(node.Pos(), node.Pos(), node.Pos(), reason)
+}
+
+func (p *processor) addErrorRange(reportAt, start, end token.Pos, reason string) {
+	report, ok := p.result[reportAt]
+	if !ok {
+		report = result{
+			reason:    reason,
+			fixRanges: []fix{},
+		}
+	}
 
-	p.result = append(p.result, Result{
-		FileName:   position.Filename,
-		LineNumber: position.Line,
-		Position:   position,
-		Reason:     reason,
+	report.fixRanges = append(report.fixRanges, fix{
+		fixRangeStart: start,
+		fixRangeEnd:   end,
 	})
+
+	p.result[reportAt] = report
 }
 
-func (p *Processor) addWarning(w string, pos token.Pos, t interface{}) {
+func (p *processor) addWarning(w string, pos token.Pos, t interface{}) {
 	position := p.fileSet.Position(pos)
 
 	p.warnings = append(p.warnings,


Test patch:
diff --git a/testdata/src/default_config/doc_examples.go b/testdata/src/default_config/doc_examples.go
new file mode 100644
index 0000000..023a7c0
--- /dev/null
+++ b/testdata/src/default_config/doc_examples.go
@@ -0,0 +1,290 @@
+package pkg
+
+import (
+	"fmt"
+	"os"
+)
+
+func AnonyMousStatement() {
+	timeout := 10
+	switch { // want "anonymous switch statements should never be cuddled"
+	case true:
+		timeout = 100
+	case false:
+		timeout = 0
+	}
+}
+
+func Append() {
+	x := []string{}
+	x = append(x, "literal")
+	notUsed := "just assigning, don't mind me"
+	x = append(x, "not z..") // want "append only allowed to cuddle with appended value"
+	useMe := "right away"
+	alsoNotUsed := ":("
+	x = append(x, "just noise") // want "append only allowed to cuddle with appended value"
+	x = append(x, useMe)
+}
+
+func Assignment() {
+	if x == 1 {
+		x = 0
+	}
+	z := x + 2 // want "assignments should only be cuddled with other assignments"
+
+	fmt.Println("x")
+	y := "x" // want "assignments should only be cuddled with other assignments"
+}
+
+func TrailingWhitespaceOrComment() {
+	return fmt.Sprintf("x")
+	// REMEMBER: add mux function later.
+
+} // want "block should not end with a whitespace"
+
+// Default is for this turned off so no diagnostic.
+func CaseTrailingWhitespace() {
+	switch n {
+	case 1:
+		fmt.Println("one")
+	case 2:
+		fmt.Println("two")
+	}
+}
+
+func StartingWhitespace() { // want "block should not start with a whitespace"
+
+
+	return
+}
+
+func BranchStatement() {
+	for i := range make([]int, 5) {
+		if i > 2 {
+			sendToOne(i)
+			sendToSecond(i)
+			continue // want "branch statements should not be cuddled if block has more than two lines"
+		}
+
+		if statement == "is short" {
+			sendToOne(i)
+			break
+		}
+	}
+}
+
+func Declarations() {
+	var eol string
+	var i int // want "declarations should never be cuddled"
+}
+
+func Defer() {
+	x := 4
+	defer notX() // want "defer statements should only be cuddled with expressions on same variable"
+
+	thisIsX := func() {}
+	defer thisIsX()
+}
+
+func ExpressionAndBlocks() {
+	t, err := someFunc()
+	if err != nil {
+		// handle error
+		return
+	}
+	fmt.Println(t) // want "expressions should not be cuddled with blocks"
+}
+
+func ExpressionAndDeclarationOrReturn() {
+	var i int
+	run() // want "expressions should not be cuddled with declarations or returns"
+
+	return i
+	run() // want "expressions should not be cuddled with declarations or returns"
+}
+
+func ForWithoutCondition() {
+	x := "hey"
+	for { // want "for statement without condition should never be cuddled"
+		fmt.Printf("x")
+	}
+}
+
+func For() {
+	x := y + 2
+	for i := 0; i < y+10; i++ { // want "for statements should only be cuddled with assignments used in the iteration"
+		fmt.Println(i)
+	}
+
+	list := getList()
+	for i, v := range anotherList { // want "ranges should only be cuddled with assignments used in the iteration"
+		fmt.Println(i)
+	}
+}
+
+func Go() {
+	thisIsFunc := func() {}
+	go thisIsNOTFunc() // want "go statements can only invoke functions assigned on line above"
+
+	thisIsFunc := func() {}
+	go thisIsFunc()
+}
+
+func IfAssign() {
+	one := 1
+	if 1 == 1 { // want "if statements should only be cuddled with assignments used in the if statement itself"
+		fmt.Println("duuh")
+	}
+	if 1 != one { // want "if statements should only be cuddled with assignments"
+		fmt.Println("i don't believe you!")
+	}
+
+	two := 2
+	if two != one {
+		fmt.Println("i don't believe you!")
+	}
+}
+
+func ExpressionAndAssignment() {
+	notInExpression := GetIt()
+	call.SomethingElse() // want "only cuddled expressions if assigning variable or using from line above"
+
+	thisIsBetter := GetIt()
+	thisIsBetter.CallIt()
+}
+
+func OnlyOneDefer() {
+	assignmentOne := Something()
+	assignmentTwo := AnotherThing()
+	defer assignmentTwo() // want "only one cuddle assignment allowed before defer statement"
+
+	f1, err := os.Open("/path/to/f1.txt")
+	if err != nil {
+		return -1
+	}
+	defer f1.Close()
+
+	m.Lock()
+	defer m.Unlock()
+}
+
+func OnlyOneFor() {
+	i := 0
+	a := 0
+	for i = 0; i < 5; i++ { // want "only one cuddle assignment allowed before for statement"
+		fmt.Println("x")
+	}
+
+	a := 0
+	i := 0
+	for i = 0; i < 5; i++ { // want "only one cuddle assignment allowed before for statement"
+		fmt.Println("x")
+	}
+}
+
+func OnlyOneGo() {
+	assignOne := SomeOne()
+	assignTwo := SomeTwo()
+	go assignTwo() // want "only one cuddle assignment allowed before go statement"
+}
+
+func OnlyOneIf() {
+	lengthA := len(sliceA)
+	lengthB := len(sliceB)
+	if lengthA != lengthB { // want "only one cuddle assignment allowed before if statement"
+		fmt.Println("not equal")
+	}
+}
+
+func OnlyOneRange() {
+	listA := GetA()
+	listB := GetB()
+	for _, v := range listB { // want "only one cuddle assignment allowed before range statement"
+		fmt.Println(v)
+	}
+}
+
+func OnlyOneSwitch() {
+	assignOne := SomeOone()
+	assignTwo := SomeTwo()
+	switch assignTwo { // want "only one cuddle assignment allowed before switch statement"
+	case 1:
+		fmt.Println("one")
+	default:
+		fmt.Println("NOT one")
+	}
+}
+
+func OnlyOneTypeSwitch() {
+	someT := GetT()
+	anotherT := GetT()
+	switch v := anotherT.(type) { // want "only one cuddle assignment allowed before type switch statement"
+	case int:
+		fmt.Println("was int")
+	default:
+		fmt.Println("was not int")
+	}
+}
+
+func RangeWithAssignment() {
+	y := []string{"a", "b", "c"}
+
+	x := 5
+	for _, v := range y { // want  "ranges should only be cuddled with assignments used in the iteration"
+		fmt.Println(v)
+	}
+
+	x := 5
+
+	y := []string{"a", "b", "c"}
+	for _, v := range y {
+		fmt.Println(v)
+	}
+}
+
+func Return() {
+	_ = func() {
+		y += 15
+		y += 15
+		return fmt.Sprintf("%s", y) // want "return statements should not be cuddled if block has more than two lines"
+	}
+
+	_ = func() {
+		y += 15
+		return fmt.Sprintf("%s", y)
+	}
+}
+
+func Switch() {
+	notInSwitch := ""
+	switch someThingElse { // want "switch statements should only be cuddled with variables switched"
+	case 1:
+		fmt.Println("one")
+	case 2:
+		fmt.Println("one")
+	case 3:
+		fmt.Println("three")
+	}
+
+	inSwitch := 2
+	switch inSwitch {
+	case 1:
+		fmt.Println("better")
+	}
+}
+
+func TypeSwitch() {
+	notInSwitch := GetSomeType()
+	switch someThingElse.(type) { // want "type switch statements should only be cuddled with variables switched"
+	case int:
+		fmt.Println("int")
+	default:
+		fmt.Println("not int")
+	}
+
+	inSwitch := GetSomeType()
+	switch inSwitch.(type) {
+	case int:
+		fmt.Println("better")
+	}
+}
diff --git a/testdata/src/default_config/doc_examples.go.golden b/testdata/src/default_config/doc_examples.go.golden
new file mode 100644
index 0000000..7426bbb
--- /dev/null
+++ b/testdata/src/default_config/doc_examples.go.golden
@@ -0,0 +1,317 @@
+package pkg
+
+import (
+	"fmt"
+	"os"
+)
+
+func AnonyMousStatement() {
+	timeout := 10
+
+	switch { // want "anonymous switch statements should never be cuddled"
+	case true:
+		timeout = 100
+	case false:
+		timeout = 0
+	}
+}
+
+func Append() {
+	x := []string{}
+	x = append(x, "literal")
+	notUsed := "just assigning, don't mind me"
+
+	x = append(x, "not z..") // want "append only allowed to cuddle with appended value"
+	useMe := "right away"
+	alsoNotUsed := ":("
+
+	x = append(x, "just noise") // want "append only allowed to cuddle with appended value"
+	x = append(x, useMe)
+}
+
+func Assignment() {
+	if x == 1 {
+		x = 0
+	}
+
+	z := x + 2 // want "assignments should only be cuddled with other assignments"
+
+	fmt.Println("x")
+
+	y := "x" // want "assignments should only be cuddled with other assignments"
+}
+
+func TrailingWhitespaceOrComment() {
+	return fmt.Sprintf("x")
+	// REMEMBER: add mux function later.
+} // want "block should not end with a whitespace"
+
+// Default is for this turned off so no diagnostic.
+func CaseTrailingWhitespace() {
+	switch n {
+	case 1:
+		fmt.Println("one")
+	case 2:
+		fmt.Println("two")
+	}
+}
+
+func StartingWhitespace() { // want "block should not start with a whitespace"
+	return
+}
+
+func BranchStatement() {
+	for i := range make([]int, 5) {
+		if i > 2 {
+			sendToOne(i)
+			sendToSecond(i)
+
+			continue // want "branch statements should not be cuddled if block has more than two lines"
+		}
+
+		if statement == "is short" {
+			sendToOne(i)
+			break
+		}
+	}
+}
+
+func Declarations() {
+	var eol string
+
+	var i int // want "declarations should never be cuddled"
+}
+
+func Defer() {
+	x := 4
+
+	defer notX() // want "defer statements should only be cuddled with expressions on same variable"
+
+	thisIsX := func() {}
+	defer thisIsX()
+}
+
+func ExpressionAndBlocks() {
+	t, err := someFunc()
+	if err != nil {
+		// handle error
+		return
+	}
+
+	fmt.Println(t) // want "expressions should not be cuddled with blocks"
+}
+
+func ExpressionAndDeclarationOrReturn() {
+	var i int
+
+	run() // want "expressions should not be cuddled with declarations or returns"
+
+	return i
+
+	run() // want "expressions should not be cuddled with declarations or returns"
+}
+
+func ForWithoutCondition() {
+	x := "hey"
+
+	for { // want "for statement without condition should never be cuddled"
+		fmt.Printf("x")
+	}
+}
+
+func For() {
+	x := y + 2
+
+	for i := 0; i < y+10; i++ { // want "for statements should only be cuddled with assignments used in the iteration"
+		fmt.Println(i)
+	}
+
+	list := getList()
+
+	for i, v := range anotherList { // want "ranges should only be cuddled with assignments used in the iteration"
+		fmt.Println(i)
+	}
+}
+
+func Go() {
+	thisIsFunc := func() {}
+
+	go thisIsNOTFunc() // want "go statements can only invoke functions assigned on line above"
+
+	thisIsFunc := func() {}
+	go thisIsFunc()
+}
+
+func IfAssign() {
+	one := 1
+
+	if 1 == 1 { // want "if statements should only be cuddled with assignments used in the if statement itself"
+		fmt.Println("duuh")
+	}
+
+	if 1 != one { // want "if statements should only be cuddled with assignments"
+		fmt.Println("i don't believe you!")
+	}
+
+	two := 2
+	if two != one {
+		fmt.Println("i don't believe you!")
+	}
+}
+
+func ExpressionAndAssignment() {
+	notInExpression := GetIt()
+
+	call.SomethingElse() // want "only cuddled expressions if assigning variable or using from line above"
+
+	thisIsBetter := GetIt()
+	thisIsBetter.CallIt()
+}
+
+func OnlyOneDefer() {
+	assignmentOne := Something()
+
+	assignmentTwo := AnotherThing()
+	defer assignmentTwo() // want "only one cuddle assignment allowed before defer statement"
+
+	f1, err := os.Open("/path/to/f1.txt")
+	if err != nil {
+		return -1
+	}
+	defer f1.Close()
+
+	m.Lock()
+	defer m.Unlock()
+}
+
+func OnlyOneFor() {
+	i := 0
+	a := 0
+
+	for i = 0; i < 5; i++ { // want "only one cuddle assignment allowed before for statement"
+		fmt.Println("x")
+	}
+
+	a := 0
+
+	i := 0
+	for i = 0; i < 5; i++ { // want "only one cuddle assignment allowed before for statement"
+		fmt.Println("x")
+	}
+}
+
+func OnlyOneGo() {
+	assignOne := SomeOne()
+
+	assignTwo := SomeTwo()
+	go assignTwo() // want "only one cuddle assignment allowed before go statement"
+}
+
+func OnlyOneIf() {
+	lengthA := len(sliceA)
+	lengthB := len(sliceB)
+
+	if lengthA != lengthB { // want "only one cuddle assignment allowed before if statement"
+		fmt.Println("not equal")
+	}
+}
+
+func OnlyOneRange() {
+	listA := GetA()
+
+	listB := GetB()
+	for _, v := range listB { // want "only one cuddle assignment allowed before range statement"
+		fmt.Println(v)
+	}
+}
+
+func OnlyOneSwitch() {
+	assignOne := SomeOone()
+
+	assignTwo := SomeTwo()
+	switch assignTwo { // want "only one cuddle assignment allowed before switch statement"
+	case 1:
+		fmt.Println("one")
+	default:
+		fmt.Println("NOT one")
+	}
+}
+
+func OnlyOneTypeSwitch() {
+	someT := GetT()
+
+	anotherT := GetT()
+	switch v := anotherT.(type) { // want "only one cuddle assignment allowed before type switch statement"
+	case int:
+		fmt.Println("was int")
+	default:
+		fmt.Println("was not int")
+	}
+}
+
+func RangeWithAssignment() {
+	y := []string{"a", "b", "c"}
+
+	x := 5
+
+	for _, v := range y { // want  "ranges should only be cuddled with assignments used in the iteration"
+		fmt.Println(v)
+	}
+
+	x := 5
+
+	y := []string{"a", "b", "c"}
+	for _, v := range y {
+		fmt.Println(v)
+	}
+}
+
+func Return() {
+	_ = func() {
+		y += 15
+		y += 15
+
+		return fmt.Sprintf("%s", y) // want "return statements should not be cuddled if block has more than two lines"
+	}
+
+	_ = func() {
+		y += 15
+		return fmt.Sprintf("%s", y)
+	}
+}
+
+func Switch() {
+	notInSwitch := ""
+
+	switch someThingElse { // want "switch statements should only be cuddled with variables switched"
+	case 1:
+		fmt.Println("one")
+	case 2:
+		fmt.Println("one")
+	case 3:
+		fmt.Println("three")
+	}
+
+	inSwitch := 2
+	switch inSwitch {
+	case 1:
+		fmt.Println("better")
+	}
+}
+
+func TypeSwitch() {
+	notInSwitch := GetSomeType()
+
+	switch someThingElse.(type) { // want "type switch statements should only be cuddled with variables switched"
+	case int:
+		fmt.Println("int")
+	default:
+		fmt.Println("not int")
+	}
+
+	inSwitch := GetSomeType()
+	switch inSwitch.(type) {
+	case int:
+		fmt.Println("better")
+	}
+}
diff --git a/testdata/src/default_config/fix_advanced.go b/testdata/src/default_config/fix_advanced.go
new file mode 100644
index 0000000..4ba14c8
--- /dev/null
+++ b/testdata/src/default_config/fix_advanced.go
@@ -0,0 +1,22 @@
+package testpkg
+
+func Advanced() {
+	var foo = 1
+	var bar = 2    // want "declarations should never be cuddled"
+	var biz int // want "declarations should never be cuddled"
+
+	x := []string{}
+	x = append(x, "literal")
+	notUsed := "just assigning, don't mind me"
+	x = append(x, "not z..") // want "append only allowed to cuddle with appended value"
+	useMe := "right away"
+	alsoNotUsed := ":("
+	x = append(x, "just noise") // want "append only allowed to cuddle with appended value"
+	x = append(x, useMe)
+
+	_ = foo
+	_ = bar
+	_ = biz
+	_ = notUsed
+	_ = alsoNotUsed
+}
diff --git a/testdata/src/default_config/fix_advanced.go.golden b/testdata/src/default_config/fix_advanced.go.golden
new file mode 100644
index 0000000..eae2712
--- /dev/null
+++ b/testdata/src/default_config/fix_advanced.go.golden
@@ -0,0 +1,26 @@
+package testpkg
+
+func Advanced() {
+	var foo = 1
+
+	var bar = 2 // want "declarations should never be cuddled"
+
+	var biz int // want "declarations should never be cuddled"
+
+	x := []string{}
+	x = append(x, "literal")
+	notUsed := "just assigning, don't mind me"
+
+	x = append(x, "not z..") // want "append only allowed to cuddle with appended value"
+	useMe := "right away"
+	alsoNotUsed := ":("
+
+	x = append(x, "just noise") // want "append only allowed to cuddle with appended value"
+	x = append(x, useMe)
+
+	_ = foo
+	_ = bar
+	_ = biz
+	_ = notUsed
+	_ = alsoNotUsed
+}
diff --git a/testdata/src/default_config/fix_cuddle_blocks.go b/testdata/src/default_config/fix_cuddle_blocks.go
new file mode 100644
index 0000000..0c7f1a4
--- /dev/null
+++ b/testdata/src/default_config/fix_cuddle_blocks.go
@@ -0,0 +1,62 @@
+package testpkg
+
+import "fmt"
+
+func FailBlocks() {
+	if false {
+		return
+	}
+	_, err := maybeErr() // want "assignments should only be cuddled with other assignments"
+	if err != nil {      // want "only one cuddle assignment allowed before if statement"
+		return
+	}
+
+	if true {
+		return
+	}
+	_, x := maybeErr() // want "assignments should only be cuddled with other assignments"
+	if err != nil {    // want "only one cuddle assignment allowed before if statement"
+		return
+	}
+
+	x = nil
+	_, err = maybeErr()
+	if err != nil { // want "only one cuddle assignment allowed before if statement"
+		panic(err)
+	}
+
+	if x != nil {
+	}
+	switch 1 { // want "anonymous switch statements should never be cuddled"
+	case 1:
+		fmt.Println("one")
+	case 2:
+		fmt.Println("two")
+	case 3:
+		fmt.Println("three")
+	}
+
+	foo := 1
+	bar := 2
+	baz := 3
+	if 1 == 1 { // want "only one cuddle assignment allowed before if statement"
+		baz = 4
+	}
+
+	_ = foo
+	_ = bar
+	_ = baz
+
+	a := 1
+	b := 2
+	for b < bar { // want "only one cuddle assignment allowed before for statement"
+		b--
+	}
+
+	_ = a
+	_ = b
+}
+
+func maybeErr() (int, error) {
+	return 0, nil
+}
diff --git a/testdata/src/default_config/fix_cuddle_blocks.go.golden b/testdata/src/default_config/fix_cuddle_blocks.go.golden
new file mode 100644
index 0000000..7b91d40
--- /dev/null
+++ b/testdata/src/default_config/fix_cuddle_blocks.go.golden
@@ -0,0 +1,69 @@
+package testpkg
+
+import "fmt"
+
+func FailBlocks() {
+	if false {
+		return
+	}
+
+	_, err := maybeErr() // want "assignments should only be cuddled with other assignments"
+	if err != nil {      // want "only one cuddle assignment allowed before if statement"
+		return
+	}
+
+	if true {
+		return
+	}
+
+	_, x := maybeErr() // want "assignments should only be cuddled with other assignments"
+
+	if err != nil { // want "only one cuddle assignment allowed before if statement"
+		return
+	}
+
+	x = nil
+
+	_, err = maybeErr()
+	if err != nil { // want "only one cuddle assignment allowed before if statement"
+		panic(err)
+	}
+
+	if x != nil {
+	}
+
+	switch 1 { // want "anonymous switch statements should never be cuddled"
+	case 1:
+		fmt.Println("one")
+	case 2:
+		fmt.Println("two")
+	case 3:
+		fmt.Println("three")
+	}
+
+	foo := 1
+	bar := 2
+
+	baz := 3
+	if 1 == 1 { // want "only one cuddle assignment allowed before if statement"
+		baz = 4
+	}
+
+	_ = foo
+	_ = bar
+	_ = baz
+
+	a := 1
+
+	b := 2
+	for b < bar { // want "only one cuddle assignment allowed before for statement"
+		b--
+	}
+
+	_ = a
+	_ = b
+}
+
+func maybeErr() (int, error) {
+	return 0, nil
+}
diff --git a/testdata/src/default_config/generic_handling.go b/testdata/src/default_config/generic_handling.go
new file mode 100644
index 0000000..f09ce52
--- /dev/null
+++ b/testdata/src/default_config/generic_handling.go
@@ -0,0 +1,559 @@
+package pkg
+
+import (
+	"encoding/json"
+	"fmt"
+	"strconv"
+	"sync"
+	"time"
+)
+
+func OnlyComments() {
+	// Also signel comments are ignored
+	// foo := false
+	// if true {
+	//
+	//	foo = true
+	//
+	// }
+}
+
+func NoBlock(r string) (x uintptr)
+
+func CommentOnFnLine() { // This is just a comment
+	fmt.Println("hello")
+}
+
+func ReturnOnSameLine() error {
+	s := func() error { return nil }
+	_ = s
+}
+
+func LabeledBlocks() {
+	goto end
+end:
+}
+
+func If() {
+	v, err := strconv.Atoi("1")
+	if err != nil {
+		fmt.Println(v)
+	}
+
+	a := true
+	if a {
+		fmt.Println("I'm OK")
+	}
+
+	a, b := true, false
+	if !a {
+		fmt.Println("I'm OK")
+	}
+
+	a, b = true, false
+	if err != nil { // want "if statements should only be cuddled with assignments used in the if statement itself"
+		fmt.Println("I'm not OK")
+	}
+
+	if true {
+		fmt.Println("I'm OK")
+	}
+	if false { // want "if statements should only be cuddled with assignments"
+		fmt.Println("I'm OK")
+	}
+
+	c := true
+	fooBar := "string"
+	a, b := true, false
+	if a && b || !c && len(fooBar) > 2 { // want "only one cuddle assignment allowed before if statement"
+		return false
+	}
+	a, b, c := someFunc()          // want "assignments should only be cuddled with other assignments"
+	if z && len(y) > 0 || 3 == 4 { // want "only one cuddle assignment allowed before if statement"
+		return true
+	}
+
+	a, b := true, false
+	if c && b || !c && len(fooBar) > 2 {
+		return false
+	}
+
+	r := map[string]interface{}{}
+	if err := json.NewDecoder(someReader).Decode(&r); err != nil {
+		return "this should be OK"
+	}
+}
+
+func Return() {
+	if true {
+		if false {
+			//
+		}
+		return // want "return statements should not be cuddled if block has more than two lines"
+	}
+
+	if false {
+		fmt.Println("this is less than two lines")
+		return
+	}
+	return // want "return statements should not be cuddled if block has more than two lines"
+}
+
+func AssignmentAndDeclaration() {
+	if true {
+		fmt.Println("I'm not OK")
+	}
+	foo := true // want "assignments should only be cuddled with other assignments"
+
+	bar := foo
+	baz := true
+	biz := false
+
+	var a bool
+	fmt.Println(a) // want "expressions should not be cuddled with declarations or returns"
+
+	b := true
+	fmt.Println(b)
+}
+
+func Range() {
+	anotherList := make([]string, 5)
+
+	myList := make([]int, 10)
+	for i := range anotherList { // want "ranges should only be cuddled with assignments used in the iteration"
+		fmt.Println(i)
+	}
+
+	myList = make([]string, 5)
+
+	anotherList = make([]int, 10)
+	for i := range anotherList {
+		fmt.Println(i)
+	}
+
+	someList, anotherList := GetTwoListsOfStrings()
+	for i := range append(someList, anotherList...) {
+		fmt.Println(i)
+	}
+
+	aThirdList := GetList()
+	for i := range append(someList, aThirdList...) {
+		fmt.Println(i)
+	}
+}
+
+func FirstInBlock() {
+	idx := i
+	if i > 0 {
+		idx = i - 1
+	}
+
+	vals := map[int]struct{}{}
+	for i := range make([]int, 5) {
+		vals[i] = struct{}{}
+	}
+
+	x := []int{}
+
+	vals := map[int]struct{}{}
+	for i := range make([]int, 5) { // want "ranges should only be cuddled with assignments used in the iteration"
+		x = append(x, i)
+	}
+}
+
+func OnlyCuddleOneAssignment() {
+	foo := true
+	bar := false
+
+	biz := true || false
+	if biz {
+		return true
+	}
+
+	foo := true
+	bar := false
+	biz := true || false
+
+	if biz {
+		return true
+	}
+
+	foo := true
+	bar := false
+	biz := true || false
+	if biz { // want "only one cuddle assignment allowed before if statement"
+		return false
+	}
+}
+
+func IdentifiersWithIndices() {
+	runes := []rune{'+', '-'}
+	if runes[0] == '+' || runes[0] == '-' {
+		return string(runes[1:])
+	}
+
+	listTwo := []string{}
+
+	listOne := []string{"one"}
+	if listOne[0] == "two" {
+		return "not allowed"
+	}
+
+	for i := range make([]int, 10) {
+		key := GetKey()
+		if val, ok := someMap[key]; ok {
+			fmt.Println("ok!")
+		}
+
+		someOtherMap := GetMap()
+		if val, ok := someOtherMap[key]; ok {
+			fmt.Println("ok")
+		}
+
+		someIndex := 3
+		if val := someSlice[someIndex]; val != nil {
+			retunr
+		}
+	}
+}
+
+func Defer() {
+	thingOne := getOne()
+	thingTwo := getTwo()
+
+	defer thingOne.Close()
+	defer thingTwo.Close()
+
+	thingOne := getOne()
+	defer thingOne.Close()
+
+	thingTwo := getTwo()
+	defer thingTwo.Close()
+
+	thingOne := getOne()
+	defer thingOne.Close()
+	thingTwo := getTwo()   // want "assignments should only be cuddled with other assignments"
+	defer thingTwo.Close() // want "only one cuddle assignment allowed before defer statement"
+
+	thingOne := getOne()
+	thingTwo := getTwo()
+	defer thingOne.Close() // want "only one cuddle assignment allowed before defer statement"
+	defer thingTwo.Close()
+
+	m := sync.Mutex{}
+
+	m.Lock()
+	defer m.Unlock()
+
+	foo := true
+	defer func(b bool) { // want "defer statements should only be cuddled with expressions on same variable"
+		fmt.Printf("%v", b)
+	}()
+}
+
+func For() {
+	bool := true
+	for { // want "for statement without condition should never be cuddled"
+		fmt.Println("should not be allowed")
+
+		if bool {
+			break
+		}
+	}
+}
+
+func Go() {
+	go func() {
+		panic("is this real life?")
+	}()
+
+	fooFunc := func() {}
+	go fooFunc()
+
+	barFunc := func() {}
+	go fooFunc() // want "go statements can only invoke functions assigned on line above"
+
+	go func() {
+		fmt.Println("hey")
+	}()
+
+	cuddled := true
+	go func() { // want "go statements can only invoke functions assigned on line above"
+		fmt.Println("hey")
+	}()
+
+	argToGo := 1
+	go takesArg(argToGo)
+
+	notArgToGo := 1
+	go takesArg(argToGo) // want "go statements can only invoke functions assigned on line above"
+
+	t1 := NewT()
+	t2 := NewT()
+	t3 := NewT()
+
+	go t1()
+	go t2()
+	go t3()
+
+	multiCuddle1 := NewT()
+	multiCuddle2 := NewT()
+	go multiCuddle2() // want "only one cuddle assignment allowed before go"
+
+	t4 := NewT()
+	t5 := NewT()
+	go t5() // want "only one cuddle assignment allowed before go"
+	go t4()
+}
+
+func Switch() {
+	var b bool
+	switch b {
+	case true:
+		return "a"
+	case false:
+		return "b"
+	}
+
+	t := time.Now()
+
+	switch {
+	case t.Hour() < 12:
+		fmt.Println("It's before noon")
+	default:
+		fmt.Println("It's after noon")
+	}
+
+	var b bool
+	switch anotherBool { // want "switch statements should only be cuddled with variables switched"
+	case true:
+		return "a"
+	case false:
+		return "b"
+	}
+
+	t := time.Now()
+	switch { // want "anonymous switch statements should never be cuddled"
+	case t.Hour() < 12:
+		fmt.Println("It's before noon")
+	default:
+		fmt.Println("It's after noon")
+	}
+}
+
+func TypeSwitch() {
+	x := GetSome()
+	switch v := x.(type) {
+	case int:
+		return "got int"
+	default:
+		return "got other"
+	}
+
+	var id string
+	switch i := objectID.(type) {
+	case int:
+		id = strconv.Itoa(i)
+	case uint32:
+		id = strconv.Itoa(int(i))
+	case string:
+		id = i
+	}
+
+	var b bool
+	switch AnotherVal.(type) { // want "type switch statements should only be cuddled with variables switched"
+	case int:
+		return "a"
+	case string:
+		return "b"
+	}
+}
+
+func Append() {
+	someList := []string{}
+
+	bar := "baz"
+	someList = append(someList, bar)
+
+	bar := "baz"
+	someList = append(someList, "notBar") // want "append only allowed to cuddle with appended value"
+
+	bar := "baz"
+	someList = append(someList, fmt.Sprintf("%s", bar))
+
+	bar := "baz"
+	whatever := appendFn(bar)
+
+	s := []string{}
+
+	s = append(s, "one")
+	s = append(s, "two")
+	s = append(s, "three")
+
+	p.defs = append(p.defs, x)
+	def.parseFrom(p)
+	p.defs = append(p.defs, def)
+	def.parseFrom(p)
+	def.parseFrom(p)
+	p.defs = append(p.defs, x)
+}
+
+func ExpressionAssignment() {
+	foo := true
+	someFunc(foo)
+
+	foo := true
+	someFunc(false) // want "only cuddled expressions if assigning variable or using from line above"
+}
+
+func Channel() {
+	timeoutCh := time.After(timeout)
+
+	for range make([]int, 10) {
+		select {
+		case <-timeoutCh:
+			return true
+		case <-time.After(10 * time.Millisecond):
+			return false
+		}
+	}
+
+	select {
+	case <-time.After(1 * time.Second):
+		return "1s"
+	default:
+		return "are we there yet?"
+	}
+}
+
+func Branch() {
+	for {
+		if true {
+			singleLine := true
+			break
+		}
+
+		if true && false {
+			multiLine := true
+			maybeVar := "var"
+			continue // want "branch statements should not be cuddled if block has more than two lines"
+		}
+
+		if false {
+			multiLine := true
+			maybeVar := "var"
+
+			break
+		}
+	}
+}
+
+func Locks() {
+	hashFileCache.Lock()
+	out, ok := hashFileCache.m[file]
+	hashFileCache.Unlock()
+
+	mu := &sync.Mutex{}
+	mu.X(y).Z.RLock()
+	x, y := someMap[someKey]
+	mu.RUnlock()
+}
+
+func SpliceAndSlice() {
+	start := 0
+	if v := aSlice[start:3]; v {
+		fmt.Println("")
+	}
+
+	someKey := "str"
+	if v, ok := someMap[obtain(someKey)+"str"]; ok {
+		fmt.Println("Hey there")
+	}
+
+	end := 10
+	if v := arr[3:notEnd]; !v { // want "if statements should only be cuddled with assignments used in the if statement itself"
+		// Error
+	}
+
+	notKey := "str"
+	if v, ok := someMap[someKey]; ok { // want "if statements should only be cuddled with assignments used in the if statement itself"
+		// Error
+	}
+}
+
+func Block() {
+	var foo string
+
+	x := func() {
+		var err error
+		foo = "1" // want "assignments should only be cuddled with other assignments"
+	}()
+
+	x := func() {
+		var err error
+		foo = "1" // want "assignments should only be cuddled with other assignments"
+	}
+
+	func() {
+		var err error
+		foo = "1" // want "assignments should only be cuddled with other assignments"
+	}()
+
+	func() {
+		var err error
+		foo = "1" // want "assignments should only be cuddled with other assignments"
+	}
+
+	func() {
+		func() {
+			return func() {
+				var err error
+				foo = "1" // want "assignments should only be cuddled with other assignments"
+			}
+		}()
+	}
+
+	var x error
+	foo, err := func() { return "", nil } // want "assignments should only be cuddled with other assignments"
+
+	defer func() {
+		var err error
+		foo = "1" // want "assignments should only be cuddled with other assignments"
+	}()
+
+	go func() {
+		var err error
+		foo = "1" // want "assignments should only be cuddled with other assignments"
+	}()
+}
+
+func CommentInLast() {
+	switch nextStatement.(type) {
+	case *someType, *anotherType:
+	default:
+		// Foo
+		return
+	}
+}
+
+func OnlyCheckTwoLinesAboveIfAssignment() {
+	if err != nil {
+		return
+	}
+	a, err := someFn() // want "assignments should only be cuddled with other assignments"
+	if err != nil {    // want "only one cuddle assignment allowed before if statement"
+		return result, err
+	}
+	b := someFn()                 // want "assignments should only be cuddled with other assignments"
+	if err := fn(b); err != nil { // want "only one cuddle assignment allowed before if statement"
+		return
+	}
+
+	if true {
+		return
+	}
+	c, err := someFn() // want "assignments should only be cuddled with other assignments"
+	if err != nil {    // want "only one cuddle assignment allowed before if statement"
+		return result, err
+	}
+}
diff --git a/testdata/src/default_config/generic_handling.go.golden b/testdata/src/default_config/generic_handling.go.golden
new file mode 100644
index 0000000..91ffc5a
--- /dev/null
+++ b/testdata/src/default_config/generic_handling.go.golden
@@ -0,0 +1,599 @@
+package pkg
+
+import (
+	"encoding/json"
+	"fmt"
+	"strconv"
+	"sync"
+	"time"
+)
+
+func OnlyComments() {
+	// Also signel comments are ignored
+	// foo := false
+	// if true {
+	//
+	//	foo = true
+	//
+	// }
+}
+
+func NoBlock(r string) (x uintptr)
+
+func CommentOnFnLine() { // This is just a comment
+	fmt.Println("hello")
+}
+
+func ReturnOnSameLine() error {
+	s := func() error { return nil }
+	_ = s
+}
+
+func LabeledBlocks() {
+	goto end
+end:
+}
+
+func If() {
+	v, err := strconv.Atoi("1")
+	if err != nil {
+		fmt.Println(v)
+	}
+
+	a := true
+	if a {
+		fmt.Println("I'm OK")
+	}
+
+	a, b := true, false
+	if !a {
+		fmt.Println("I'm OK")
+	}
+
+	a, b = true, false
+
+	if err != nil { // want "if statements should only be cuddled with assignments used in the if statement itself"
+		fmt.Println("I'm not OK")
+	}
+
+	if true {
+		fmt.Println("I'm OK")
+	}
+
+	if false { // want "if statements should only be cuddled with assignments"
+		fmt.Println("I'm OK")
+	}
+
+	c := true
+	fooBar := "string"
+	a, b := true, false
+
+	if a && b || !c && len(fooBar) > 2 { // want "only one cuddle assignment allowed before if statement"
+		return false
+	}
+
+	a, b, c := someFunc() // want "assignments should only be cuddled with other assignments"
+
+	if z && len(y) > 0 || 3 == 4 { // want "only one cuddle assignment allowed before if statement"
+		return true
+	}
+
+	a, b := true, false
+	if c && b || !c && len(fooBar) > 2 {
+		return false
+	}
+
+	r := map[string]interface{}{}
+	if err := json.NewDecoder(someReader).Decode(&r); err != nil {
+		return "this should be OK"
+	}
+}
+
+func Return() {
+	if true {
+		if false {
+			//
+		}
+
+		return // want "return statements should not be cuddled if block has more than two lines"
+	}
+
+	if false {
+		fmt.Println("this is less than two lines")
+		return
+	}
+
+	return // want "return statements should not be cuddled if block has more than two lines"
+}
+
+func AssignmentAndDeclaration() {
+	if true {
+		fmt.Println("I'm not OK")
+	}
+
+	foo := true // want "assignments should only be cuddled with other assignments"
+
+	bar := foo
+	baz := true
+	biz := false
+
+	var a bool
+
+	fmt.Println(a) // want "expressions should not be cuddled with declarations or returns"
+
+	b := true
+	fmt.Println(b)
+}
+
+func Range() {
+	anotherList := make([]string, 5)
+
+	myList := make([]int, 10)
+
+	for i := range anotherList { // want "ranges should only be cuddled with assignments used in the iteration"
+		fmt.Println(i)
+	}
+
+	myList = make([]string, 5)
+
+	anotherList = make([]int, 10)
+	for i := range anotherList {
+		fmt.Println(i)
+	}
+
+	someList, anotherList := GetTwoListsOfStrings()
+	for i := range append(someList, anotherList...) {
+		fmt.Println(i)
+	}
+
+	aThirdList := GetList()
+	for i := range append(someList, aThirdList...) {
+		fmt.Println(i)
+	}
+}
+
+func FirstInBlock() {
+	idx := i
+	if i > 0 {
+		idx = i - 1
+	}
+
+	vals := map[int]struct{}{}
+	for i := range make([]int, 5) {
+		vals[i] = struct{}{}
+	}
+
+	x := []int{}
+
+	vals := map[int]struct{}{}
+
+	for i := range make([]int, 5) { // want "ranges should only be cuddled with assignments used in the iteration"
+		x = append(x, i)
+	}
+}
+
+func OnlyCuddleOneAssignment() {
+	foo := true
+	bar := false
+
+	biz := true || false
+	if biz {
+		return true
+	}
+
+	foo := true
+	bar := false
+	biz := true || false
+
+	if biz {
+		return true
+	}
+
+	foo := true
+	bar := false
+
+	biz := true || false
+	if biz { // want "only one cuddle assignment allowed before if statement"
+		return false
+	}
+}
+
+func IdentifiersWithIndices() {
+	runes := []rune{'+', '-'}
+	if runes[0] == '+' || runes[0] == '-' {
+		return string(runes[1:])
+	}
+
+	listTwo := []string{}
+
+	listOne := []string{"one"}
+	if listOne[0] == "two" {
+		return "not allowed"
+	}
+
+	for i := range make([]int, 10) {
+		key := GetKey()
+		if val, ok := someMap[key]; ok {
+			fmt.Println("ok!")
+		}
+
+		someOtherMap := GetMap()
+		if val, ok := someOtherMap[key]; ok {
+			fmt.Println("ok")
+		}
+
+		someIndex := 3
+		if val := someSlice[someIndex]; val != nil {
+			retunr
+		}
+	}
+}
+
+func Defer() {
+	thingOne := getOne()
+	thingTwo := getTwo()
+
+	defer thingOne.Close()
+	defer thingTwo.Close()
+
+	thingOne := getOne()
+	defer thingOne.Close()
+
+	thingTwo := getTwo()
+	defer thingTwo.Close()
+
+	thingOne := getOne()
+	defer thingOne.Close()
+
+	thingTwo := getTwo()   // want "assignments should only be cuddled with other assignments"
+	defer thingTwo.Close() // want "only one cuddle assignment allowed before defer statement"
+
+	thingOne := getOne()
+	thingTwo := getTwo()
+
+	defer thingOne.Close() // want "only one cuddle assignment allowed before defer statement"
+	defer thingTwo.Close()
+
+	m := sync.Mutex{}
+
+	m.Lock()
+	defer m.Unlock()
+
+	foo := true
+
+	defer func(b bool) { // want "defer statements should only be cuddled with expressions on same variable"
+		fmt.Printf("%v", b)
+	}()
+}
+
+func For() {
+	bool := true
+
+	for { // want "for statement without condition should never be cuddled"
+		fmt.Println("should not be allowed")
+
+		if bool {
+			break
+		}
+	}
+}
+
+func Go() {
+	go func() {
+		panic("is this real life?")
+	}()
+
+	fooFunc := func() {}
+	go fooFunc()
+
+	barFunc := func() {}
+
+	go fooFunc() // want "go statements can only invoke functions assigned on line above"
+
+	go func() {
+		fmt.Println("hey")
+	}()
+
+	cuddled := true
+
+	go func() { // want "go statements can only invoke functions assigned on line above"
+		fmt.Println("hey")
+	}()
+
+	argToGo := 1
+	go takesArg(argToGo)
+
+	notArgToGo := 1
+
+	go takesArg(argToGo) // want "go statements can only invoke functions assigned on line above"
+
+	t1 := NewT()
+	t2 := NewT()
+	t3 := NewT()
+
+	go t1()
+	go t2()
+	go t3()
+
+	multiCuddle1 := NewT()
+
+	multiCuddle2 := NewT()
+	go multiCuddle2() // want "only one cuddle assignment allowed before go"
+
+	t4 := NewT()
+
+	t5 := NewT()
+	go t5() // want "only one cuddle assignment allowed before go"
+	go t4()
+}
+
+func Switch() {
+	var b bool
+	switch b {
+	case true:
+		return "a"
+	case false:
+		return "b"
+	}
+
+	t := time.Now()
+
+	switch {
+	case t.Hour() < 12:
+		fmt.Println("It's before noon")
+	default:
+		fmt.Println("It's after noon")
+	}
+
+	var b bool
+
+	switch anotherBool { // want "switch statements should only be cuddled with variables switched"
+	case true:
+		return "a"
+	case false:
+		return "b"
+	}
+
+	t := time.Now()
+
+	switch { // want "anonymous switch statements should never be cuddled"
+	case t.Hour() < 12:
+		fmt.Println("It's before noon")
+	default:
+		fmt.Println("It's after noon")
+	}
+}
+
+func TypeSwitch() {
+	x := GetSome()
+	switch v := x.(type) {
+	case int:
+		return "got int"
+	default:
+		return "got other"
+	}
+
+	var id string
+	switch i := objectID.(type) {
+	case int:
+		id = strconv.Itoa(i)
+	case uint32:
+		id = strconv.Itoa(int(i))
+	case string:
+		id = i
+	}
+
+	var b bool
+
+	switch AnotherVal.(type) { // want "type switch statements should only be cuddled with variables switched"
+	case int:
+		return "a"
+	case string:
+		return "b"
+	}
+}
+
+func Append() {
+	someList := []string{}
+
+	bar := "baz"
+	someList = append(someList, bar)
+
+	bar := "baz"
+
+	someList = append(someList, "notBar") // want "append only allowed to cuddle with appended value"
+
+	bar := "baz"
+	someList = append(someList, fmt.Sprintf("%s", bar))
+
+	bar := "baz"
+	whatever := appendFn(bar)
+
+	s := []string{}
+
+	s = append(s, "one")
+	s = append(s, "two")
+	s = append(s, "three")
+
+	p.defs = append(p.defs, x)
+	def.parseFrom(p)
+	p.defs = append(p.defs, def)
+	def.parseFrom(p)
+	def.parseFrom(p)
+	p.defs = append(p.defs, x)
+}
+
+func ExpressionAssignment() {
+	foo := true
+	someFunc(foo)
+
+	foo := true
+
+	someFunc(false) // want "only cuddled expressions if assigning variable or using from line above"
+}
+
+func Channel() {
+	timeoutCh := time.After(timeout)
+
+	for range make([]int, 10) {
+		select {
+		case <-timeoutCh:
+			return true
+		case <-time.After(10 * time.Millisecond):
+			return false
+		}
+	}
+
+	select {
+	case <-time.After(1 * time.Second):
+		return "1s"
+	default:
+		return "are we there yet?"
+	}
+}
+
+func Branch() {
+	for {
+		if true {
+			singleLine := true
+			break
+		}
+
+		if true && false {
+			multiLine := true
+			maybeVar := "var"
+
+			continue // want "branch statements should not be cuddled if block has more than two lines"
+		}
+
+		if false {
+			multiLine := true
+			maybeVar := "var"
+
+			break
+		}
+	}
+}
+
+func Locks() {
+	hashFileCache.Lock()
+	out, ok := hashFileCache.m[file]
+	hashFileCache.Unlock()
+
+	mu := &sync.Mutex{}
+	mu.X(y).Z.RLock()
+	x, y := someMap[someKey]
+	mu.RUnlock()
+}
+
+func SpliceAndSlice() {
+	start := 0
+	if v := aSlice[start:3]; v {
+		fmt.Println("")
+	}
+
+	someKey := "str"
+	if v, ok := someMap[obtain(someKey)+"str"]; ok {
+		fmt.Println("Hey there")
+	}
+
+	end := 10
+
+	if v := arr[3:notEnd]; !v { // want "if statements should only be cuddled with assignments used in the if statement itself"
+		// Error
+	}
+
+	notKey := "str"
+
+	if v, ok := someMap[someKey]; ok { // want "if statements should only be cuddled with assignments used in the if statement itself"
+		// Error
+	}
+}
+
+func Block() {
+	var foo string
+
+	x := func() {
+		var err error
+
+		foo = "1" // want "assignments should only be cuddled with other assignments"
+	}()
+
+	x := func() {
+		var err error
+
+		foo = "1" // want "assignments should only be cuddled with other assignments"
+	}
+
+	func() {
+		var err error
+
+		foo = "1" // want "assignments should only be cuddled with other assignments"
+	}()
+
+	func() {
+		var err error
+
+		foo = "1" // want "assignments should only be cuddled with other assignments"
+	}
+
+	func() {
+		func() {
+			return func() {
+				var err error
+
+				foo = "1" // want "assignments should only be cuddled with other assignments"
+			}
+		}()
+	}
+
+	var x error
+
+	foo, err := func() { return "", nil } // want "assignments should only be cuddled with other assignments"
+
+	defer func() {
+		var err error
+
+		foo = "1" // want "assignments should only be cuddled with other assignments"
+	}()
+
+	go func() {
+		var err error
+
+		foo = "1" // want "assignments should only be cuddled with other assignments"
+	}()
+}
+
+func CommentInLast() {
+	switch nextStatement.(type) {
+	case *someType, *anotherType:
+	default:
+		// Foo
+		return
+	}
+}
+
+func OnlyCheckTwoLinesAboveIfAssignment() {
+	if err != nil {
+		return
+	}
+
+	a, err := someFn() // want "assignments should only be cuddled with other assignments"
+	if err != nil {    // want "only one cuddle assignment allowed before if statement"
+		return result, err
+	}
+
+	b := someFn()                 // want "assignments should only be cuddled with other assignments"
+	if err := fn(b); err != nil { // want "only one cuddle assignment allowed before if statement"
+		return
+	}
+
+	if true {
+		return
+	}
+
+	c, err := someFn() // want "assignments should only be cuddled with other assignments"
+	if err != nil {    // want "only one cuddle assignment allowed before if statement"
+		return result, err
+	}
+}
diff --git a/testdata/src/default_config/multiline_case.go b/testdata/src/default_config/multiline_case.go
new file mode 100644
index 0000000..af193c7
--- /dev/null
+++ b/testdata/src/default_config/multiline_case.go
@@ -0,0 +1,32 @@
+package pkg
+
+func Fn() {
+	switch {
+	case true, false:
+		fmt.Println("ok")
+	case true || false:
+		fmt.Println("ok")
+	case true, false:
+		fmt.Println("ok")
+	case true || false:
+		fmt.Println("ok")
+	case true,
+		false: // want "block should not start with a whitespace"
+
+		fmt.Println("starting whitespace multiline case")
+	case true ||
+		false: // want "block should not start with a whitespace"
+
+		fmt.Println("starting whitespace multiline case")
+	case true,
+		false:
+		fmt.Println("ending whitespace multiline case")
+
+	case true ||
+		false:
+		fmt.Println("ending whitespace multiline case")
+
+	case true, false:
+		fmt.Println("all")
+	}
+}
diff --git a/testdata/src/default_config/multiline_case.go.golden b/testdata/src/default_config/multiline_case.go.golden
new file mode 100644
index 0000000..9837d6e
--- /dev/null
+++ b/testdata/src/default_config/multiline_case.go.golden
@@ -0,0 +1,30 @@
+package pkg
+
+func Fn() {
+	switch {
+	case true, false:
+		fmt.Println("ok")
+	case true || false:
+		fmt.Println("ok")
+	case true, false:
+		fmt.Println("ok")
+	case true || false:
+		fmt.Println("ok")
+	case true,
+		false: // want "block should not start with a whitespace"
+		fmt.Println("starting whitespace multiline case")
+	case true ||
+		false: // want "block should not start with a whitespace"
+		fmt.Println("starting whitespace multiline case")
+	case true,
+		false:
+		fmt.Println("ending whitespace multiline case")
+
+	case true ||
+		false:
+		fmt.Println("ending whitespace multiline case")
+
+	case true, false:
+		fmt.Println("all")
+	}
+}
diff --git a/testdata/src/default_config/remove_whitespace.go b/testdata/src/default_config/remove_whitespace.go
new file mode 100644
index 0000000..faf4eca
--- /dev/null
+++ b/testdata/src/default_config/remove_whitespace.go
@@ -0,0 +1,83 @@
+package testpkg
+
+import "fmt"
+
+func RemoveWhitespaceNoComments() { // want "block should not start with a whitespace"
+
+	a := 1
+	if a < 2 { // want "block should not start with a whitespace"
+
+		a = 2
+
+	} // want "block should not end with a whitespace"
+
+	switch { // want "block should not start with a whitespace"
+
+	case true:
+		fmt.Println("true")
+
+	default:
+		fmt.Println("false")
+
+	} // want "block should not end with a whitespace"
+
+	_ = a
+
+} // want "block should not end with a whitespace"
+
+// RemoveWhiteSpaceWithhComments keeps comments even when removing newlines.
+func RemoveWhiteSpaceWithhComments() { // want "block should not start with a whitespace"
+	// This comment should be kept
+
+	a := 1
+	if a < 2 { // want "block should not start with a whitespace"
+
+		// This comment should be kept
+		a = 2
+		// This comment should be kept
+
+	} // want "block should not end with a whitespace"
+
+	if a > 2 { // want "block should not start with a whitespace"
+		// This comment should be kept
+
+		a = 2
+
+		// This comment should be kept
+	} // want "block should not end with a whitespace"
+
+	if a > 3 { // want "block should not start with a whitespace"
+
+		// This comment should be kept
+
+		a = 2
+
+		// This comment should be kept
+
+	} // want "block should not end with a whitespace"
+
+	_ = a
+
+	// This comment should be kept as well
+} // want "block should not end with a whitespace"
+
+func MultipleCommentsAreSorted() {
+	switch 1 {
+	case 1: // want "block should not start with a whitespace"
+		// Comment
+
+		// Comment
+		fmt.Println("1")
+	case 2: // want "block should not start with a whitespace"
+		// Comment
+
+		// Comment
+		fmt.Println("2")
+	}
+}
+
+func ExampleT() {
+	fmt.Println("output")
+
+	// Output: output
+}
diff --git a/testdata/src/default_config/remove_whitespace.go.golden b/testdata/src/default_config/remove_whitespace.go.golden
new file mode 100644
index 0000000..f1c0994
--- /dev/null
+++ b/testdata/src/default_config/remove_whitespace.go.golden
@@ -0,0 +1,65 @@
+package testpkg
+
+import "fmt"
+
+func RemoveWhitespaceNoComments() { // want "block should not start with a whitespace"
+	a := 1
+	if a < 2 { // want "block should not start with a whitespace"
+		a = 2
+	} // want "block should not end with a whitespace"
+
+	switch { // want "block should not start with a whitespace"
+	case true:
+		fmt.Println("true")
+
+	default:
+		fmt.Println("false")
+	} // want "block should not end with a whitespace"
+
+	_ = a
+} // want "block should not end with a whitespace"
+
+// RemoveWhiteSpaceWithhComments keeps comments even when removing newlines.
+func RemoveWhiteSpaceWithhComments() { // want "block should not start with a whitespace"
+	// This comment should be kept
+	a := 1
+	if a < 2 { // want "block should not start with a whitespace"
+		// This comment should be kept
+		a = 2
+		// This comment should be kept
+	} // want "block should not end with a whitespace"
+
+	if a > 2 { // want "block should not start with a whitespace"
+		// This comment should be kept
+		a = 2
+		// This comment should be kept
+	} // want "block should not end with a whitespace"
+
+	if a > 3 { // want "block should not start with a whitespace"
+		// This comment should be kept
+		a = 2
+		// This comment should be kept
+	} // want "block should not end with a whitespace"
+
+	_ = a
+	// This comment should be kept as well
+} // want "block should not end with a whitespace"
+
+func MultipleCommentsAreSorted() {
+	switch 1 {
+	case 1: // want "block should not start with a whitespace"
+		// Comment
+		// Comment
+		fmt.Println("1")
+	case 2: // want "block should not start with a whitespace"
+		// Comment
+		// Comment
+		fmt.Println("2")
+	}
+}
+
+func ExampleT() {
+	fmt.Println("output")
+
+	// Output: output
+}
diff --git a/testdata/src/wip/wip.go b/testdata/src/wip/wip.go
new file mode 100644
index 0000000..a21e3cf
--- /dev/null
+++ b/testdata/src/wip/wip.go
@@ -0,0 +1,1 @@
+package testpkg
diff --git a/testdata/src/wip/wip.go.golden b/testdata/src/wip/wip.go.golden
new file mode 100644
index 0000000..a21e3cf
--- /dev/null
+++ b/testdata/src/wip/wip.go.golden
@@ -0,0 +1,1 @@
+package testpkg
diff --git a/testdata/src/with_config/assign_and_anything/code.go b/testdata/src/with_config/assign_and_anything/code.go
new file mode 100644
index 0000000..344f838
--- /dev/null
+++ b/testdata/src/with_config/assign_and_anything/code.go
@@ -0,0 +1,13 @@
+package pkg
+
+import "fmt"
+
+func Fn() {
+	if x == 1 {
+		x = 0
+	}
+	z := x + 2
+
+	fmt.Println("x")
+	y := "x"
+}
diff --git a/testdata/src/with_config/assign_and_anything/code.go.golden b/testdata/src/with_config/assign_and_anything/code.go.golden
new file mode 100644
index 0000000..64fe3f0
--- /dev/null
+++ b/testdata/src/with_config/assign_and_anything/code.go.golden
@@ -0,0 +1,14 @@
+package pkg
+
+import "fmt"
+
+func Fn() {
+	if x == 1 {
+		x = 0
+	}
+	z := x + 2
+
+	fmt.Println("x")
+	y := "x"
+}
+
diff --git a/testdata/src/with_config/assign_and_call/code.go b/testdata/src/with_config/assign_and_call/code.go
new file mode 100644
index 0000000..d34737a
--- /dev/null
+++ b/testdata/src/with_config/assign_and_call/code.go
@@ -0,0 +1,8 @@
+package pkg
+
+func AllowAssignAndCallCuddle() {
+	p.token(':')
+	d.StartBit = p.uint() // want "assignments should only be cuddled with other assignments"
+	p.token('|')          // want "only cuddled expressions if assigning variable or using from line above"
+	d.Size = p.uint()     // want "assignments should only be cuddled with other assignments"
+}
diff --git a/testdata/src/with_config/assign_and_call/code.go.golden b/testdata/src/with_config/assign_and_call/code.go.golden
new file mode 100644
index 0000000..b245cab
--- /dev/null
+++ b/testdata/src/with_config/assign_and_call/code.go.golden
@@ -0,0 +1,11 @@
+package pkg
+
+func AllowAssignAndCallCuddle() {
+	p.token(':')
+
+	d.StartBit = p.uint() // want "assignments should only be cuddled with other assignments"
+
+	p.token('|') // want "only cuddled expressions if assigning variable or using from line above"
+
+	d.Size = p.uint() // want "assignments should only be cuddled with other assignments"
+}
diff --git a/testdata/src/with_config/case_blocks/code.go b/testdata/src/with_config/case_blocks/code.go
new file mode 100644
index 0000000..347c88a
--- /dev/null
+++ b/testdata/src/with_config/case_blocks/code.go
@@ -0,0 +1,48 @@
+package testpkg
+
+import "fmt"
+
+func Fn() {
+	switch 1 {
+	case 1: // want "block should not start with a whitespace"
+
+		// Comment
+
+		// Comment
+
+		fmt.Println("a")
+
+		// Comment
+		fmt.Println("a") // want "case block should end with newline at this size"
+	case 2: // want "block should not start with a whitespace"
+
+		fmt.Println("a")
+	case 3:
+		fmt.Println("a")
+	case 4:
+		fmt.Println("a")
+		fmt.Println("a")
+		fmt.Println("a")
+		fmt.Println("a") // want "case block should end with newline at this size"
+	case 5:
+		fmt.Println("a")
+		fmt.Println("a")
+		fmt.Println("a")
+		fmt.Println("a")
+		// Error // want "case block should end with newline at this size"
+	case 6:
+		// Comment
+		// Comment
+	case 7:
+		fmt.Println("a")
+		/*
+
+			Comment
+
+		*/
+
+		// Error // want "case block should end with newline at this size"
+	case 7:
+		fmt.Println("a")
+	}
+}
diff --git a/testdata/src/with_config/case_blocks/code.go.golden b/testdata/src/with_config/case_blocks/code.go.golden
new file mode 100644
index 0000000..f345c75
--- /dev/null
+++ b/testdata/src/with_config/case_blocks/code.go.golden
@@ -0,0 +1,48 @@
+package testpkg
+
+import "fmt"
+
+func Fn() {
+	switch 1 {
+	case 1: // want "block should not start with a whitespace"
+		// Comment
+		// Comment
+		fmt.Println("a")
+
+		// Comment
+		fmt.Println("a") // want "case block should end with newline at this size"
+
+	case 2: // want "block should not start with a whitespace"
+		fmt.Println("a")
+	case 3:
+		fmt.Println("a")
+	case 4:
+		fmt.Println("a")
+		fmt.Println("a")
+		fmt.Println("a")
+		fmt.Println("a") // want "case block should end with newline at this size"
+
+	case 5:
+		fmt.Println("a")
+		fmt.Println("a")
+		fmt.Println("a")
+		fmt.Println("a")
+		// Error // want "case block should end with newline at this size"
+
+	case 6:
+		// Comment
+		// Comment
+	case 7:
+		fmt.Println("a")
+		/*
+
+			Comment
+
+		*/
+
+		// Error // want "case block should end with newline at this size"
+
+	case 7:
+		fmt.Println("a")
+	}
+}
diff --git a/testdata/src/with_config/decl/code.go b/testdata/src/with_config/decl/code.go
new file mode 100644
index 0000000..0837e4e
--- /dev/null
+++ b/testdata/src/with_config/decl/code.go
@@ -0,0 +1,7 @@
+package pkg
+
+func Fn() {
+    var a = 1
+    var b = 2
+    var c = 3
+}
diff --git a/testdata/src/with_config/decl/code.go.golden b/testdata/src/with_config/decl/code.go.golden
new file mode 100644
index 0000000..70e8eb5
--- /dev/null
+++ b/testdata/src/with_config/decl/code.go.golden
@@ -0,0 +1,8 @@
+package pkg
+
+func Fn() {
+    var a = 1
+    var b = 2
+    var c = 3
+}
+
diff --git a/testdata/src/with_config/error_check/code.go b/testdata/src/with_config/error_check/code.go
new file mode 100644
index 0000000..67ac6b4
--- /dev/null
+++ b/testdata/src/with_config/error_check/code.go
@@ -0,0 +1,9 @@
+package pkg
+
+func Fn() {
+	err := ErrorProducingFunc()
+
+	if err != nil { // want "if statements that check an error must be cuddled with the statement that assigned the error"
+		return err
+	}
+}
diff --git a/testdata/src/with_config/error_check/code.go.golden b/testdata/src/with_config/error_check/code.go.golden
new file mode 100644
index 0000000..15fce78
--- /dev/null
+++ b/testdata/src/with_config/error_check/code.go.golden
@@ -0,0 +1,8 @@
+package pkg
+
+func Fn() {
+	err := ErrorProducingFunc()
+	if err != nil { // want "if statements that check an error must be cuddled with the statement that assigned the error"
+		return err
+	}
+}
diff --git a/testdata/src/with_config/multi_line_assign/code.go b/testdata/src/with_config/multi_line_assign/code.go
new file mode 100644
index 0000000..5f8a372
--- /dev/null
+++ b/testdata/src/with_config/multi_line_assign/code.go
@@ -0,0 +1,13 @@
+package pkg
+
+import "fmt"
+
+func AllowMultiLineAssignCuddle() {
+	x := SomeFunc(
+		"taking multiple values",
+		"suddendly not a single line",
+	)
+	if x != nil { // want "if statements should only be cuddled with assignments"
+		fmt.Println("denied")
+	}
+}
diff --git a/testdata/src/with_config/multi_line_assign/code.go.golden b/testdata/src/with_config/multi_line_assign/code.go.golden
new file mode 100644
index 0000000..044d0aa
--- /dev/null
+++ b/testdata/src/with_config/multi_line_assign/code.go.golden
@@ -0,0 +1,14 @@
+package pkg
+
+import "fmt"
+
+func AllowMultiLineAssignCuddle() {
+	x := SomeFunc(
+		"taking multiple values",
+		"suddendly not a single line",
+	)
+
+	if x != nil { // want "if statements should only be cuddled with assignments"
+		fmt.Println("denied")
+	}
+}
diff --git a/testdata/src/with_config/separate_leading_whitespace/code.go b/testdata/src/with_config/separate_leading_whitespace/code.go
new file mode 100644
index 0000000..277b797
--- /dev/null
+++ b/testdata/src/with_config/separate_leading_whitespace/code.go
@@ -0,0 +1,18 @@
+package pkg
+
+import "fmt"
+
+func Fn() {
+	// Comment one
+
+	// Comment two
+	fmt.Println("a")
+}
+
+func Fn2() { // want "block should not start with a whitespace"
+	// Comment one
+
+	// Comment two
+
+	fmt.Println("a")
+}
diff --git a/testdata/src/with_config/separate_leading_whitespace/code.go.golden b/testdata/src/with_config/separate_leading_whitespace/code.go.golden
new file mode 100644
index 0000000..dd6bda4
--- /dev/null
+++ b/testdata/src/with_config/separate_leading_whitespace/code.go.golden
@@ -0,0 +1,17 @@
+package pkg
+
+import "fmt"
+
+func Fn() {
+	// Comment one
+
+	// Comment two
+	fmt.Println("a")
+}
+
+func Fn2() { // want "block should not start with a whitespace"
+	// Comment one
+
+	// Comment two
+	fmt.Println("a")
+}
diff --git a/testdata/src/with_config/short_decl/code.go b/testdata/src/with_config/short_decl/code.go
new file mode 100644
index 0000000..142a972
--- /dev/null
+++ b/testdata/src/with_config/short_decl/code.go
@@ -0,0 +1,19 @@
+package pkg
+
+func Fn() {
+	a = "a"
+	b = "b"
+	c = "c"
+
+	a := "a"
+	b := "b"
+	c := "c"
+
+	a := "a" // want "short declaration should cuddle only with other short declarations"
+	b = "b"
+	c := "c" // want "short declaration should cuddle only with other short declarations"
+
+	a := "a" // want "short declaration should cuddle only with other short declarations"
+	b = "b"  // Hey
+	c := "c" // want "short declaration should cuddle only with other short declarations"
+}
diff --git a/testdata/src/with_config/short_decl/code.go.golden b/testdata/src/with_config/short_decl/code.go.golden
new file mode 100644
index 0000000..835051d
--- /dev/null
+++ b/testdata/src/with_config/short_decl/code.go.golden
@@ -0,0 +1,23 @@
+package pkg
+
+func Fn() {
+	a = "a"
+	b = "b"
+	c = "c"
+
+	a := "a"
+	b := "b"
+	c := "c"
+
+	a := "a" // want "short declaration should cuddle only with other short declarations"
+
+	b = "b"
+
+	c := "c" // want "short declaration should cuddle only with other short declarations"
+
+	a := "a" // want "short declaration should cuddle only with other short declarations"
+
+	b = "b" // Hey
+
+	c := "c" // want "short declaration should cuddle only with other short declarations"
+}
diff --git a/testdata/src/with_config/strict_append/code.go b/testdata/src/with_config/strict_append/code.go
new file mode 100644
index 0000000..1a64a57
--- /dev/null
+++ b/testdata/src/with_config/strict_append/code.go
@@ -0,0 +1,9 @@
+package pkg
+
+func Fn() {
+	x := []string{}
+	y := "not going in x"
+	x = append(x, "not y")
+	z := "string"
+	x = append(x, "i don't care")
+}
diff --git a/testdata/src/with_config/strict_append/code.go.golden b/testdata/src/with_config/strict_append/code.go.golden
new file mode 100644
index 0000000..961a4a3
--- /dev/null
+++ b/testdata/src/with_config/strict_append/code.go.golden
@@ -0,0 +1,10 @@
+package pkg
+
+func Fn() {
+	x := []string{}
+	y := "not going in x"
+	x = append(x, "not y")
+	z := "string"
+	x = append(x, "i don't care")
+}
+
diff --git a/testdata/src/with_config/trailing_comments/code.go b/testdata/src/with_config/trailing_comments/code.go
new file mode 100644
index 0000000..448d668
--- /dev/null
+++ b/testdata/src/with_config/trailing_comments/code.go
@@ -0,0 +1,17 @@
+package pkg
+
+import "fmt"
+
+func Fn() {
+	if true {
+		fmt.Println("a")
+		// Comment
+	}
+
+	if true {
+		fmt.Println("a")
+		// Comment
+
+		// Comment
+	} // want "block should not end with a whitespace"
+}
diff --git a/testdata/src/with_config/trailing_comments/code.go.golden b/testdata/src/with_config/trailing_comments/code.go.golden
new file mode 100644
index 0000000..3f60deb
--- /dev/null
+++ b/testdata/src/with_config/trailing_comments/code.go.golden
@@ -0,0 +1,16 @@
+package pkg
+
+import "fmt"
+
+func Fn() {
+	if true {
+		fmt.Println("a")
+		// Comment
+	}
+
+	if true {
+		fmt.Println("a")
+		// Comment
+		// Comment
+	} // want "block should not end with a whitespace"
+}
diff --git a/wsl_test.go b/wsl_test.go
index 9cc5685..ff56bc2 100644
--- a/wsl_test.go
+++ b/wsl_test.go
@@ -1,2052 +1,108 @@
 package wsl
 
 import (
-	"fmt"
+	"flag"
+	"path/filepath"
 	"testing"
 
-	"github.com/stretchr/testify/assert"
-	"github.com/stretchr/testify/require"
+	"golang.org/x/tools/go/analysis/analysistest"
 )
 
-func TestGenericHandling(t *testing.T) {
-	cases := []struct {
-		description          string
-		code                 []byte
-		expectedErrorStrings []string
-	}{
-		{
-			description: "a file must start with package declaration",
-			code: []byte(`func a() {
-				fmt.Println("this is function a")
-			}`),
-			expectedErrorStrings: []string{"invalid syntax, file cannot be linted"},
-		},
-		{
-			description: "comments are ignored, ignore this crappy code",
-			code: []byte(`package main
-
-			/*
-			Multi line comments ignore errors
-			if true {
-
-				fmt.Println("Well hello blankies!")
-
-			}
-			*/
-			func main() {
-				// Also signel comments are ignored
-				// foo := false
-				// if true {
-				//
-				//	foo = true
-				//
-				// }
-			}`),
-		},
-		{
-			description: "do not panic on empty blocks",
-			code: []byte(`package main
-
-			func x(r string) (x uintptr)`),
-		},
-		{
-			description: "no false positives for comments",
-			code: []byte(`package main
+func TestWIP(t *testing.T) {
+	testdata := analysistest.TestData()
+	analyzer := Analyzer
+	analyzer.Flags = flags()
 
-			func main() { // This is a comment, not the first line
-				fmt.Println("hello, world")
-			}`),
-		},
-		{
-			description: "no false positives for one line functions",
-			code: []byte(`package main
-
-			func main() {
-				s := func() error { return nil }
-			}`),
-		},
-		{
-			description: "no false positives for empty labeled blocks",
-			code: []byte(`package main
-			func main() {
-				goto end;
-				end:
-			}`),
-		},
-	}
-
-	for _, tc := range cases {
-		t.Run(tc.description, func(t *testing.T) {
-			p := NewProcessor()
-			p.process("unit-test", tc.code)
-
-			require.Len(t, p.result, len(tc.expectedErrorStrings), "correct amount of errors found")
-
-			for i := range tc.expectedErrorStrings {
-				assert.Contains(t, p.result[i].Reason, tc.expectedErrorStrings[i], "expected error found")
-			}
-		})
-	}
+	analysistest.RunWithSuggestedFixes(t, testdata, analyzer, "wip")
 }
 
-func TestShouldRemoveEmptyLines(t *testing.T) {
-	cases := []struct {
-		description          string
-		code                 []byte
-		expectedErrorStrings []string
-	}{
-		{
-			description: "if statements cannot start or end with newlines",
-			code: []byte(`package main
-			func main() {
-				if true {
-
-					fmt.Println("this violates the rules")
-
-				}
-			}`),
-			expectedErrorStrings: []string{"block should not start with a whitespace", reasonBlockEndsWithWS},
-		},
-		{
-			description: "whitespaces parsed correct even with comments (whitespace found)",
-			code: []byte(`package main
-			func main() {
-				if true {
-					// leading comment, still too much whitespace
-
-					fmt.Println("this violates the rules")
-
-					// trailing comment, still too much whitespace
-				}
-			}`),
-			expectedErrorStrings: []string{"block should not start with a whitespace", reasonBlockEndsWithWS},
-		},
-		{
-			description: "whitespaces parsed correct even with comments (whitespace NOT found)",
-			code: []byte(`package main
-
-			func main() {
-				if true {
-					// leading comment, still too much whitespace
-					// more comments
-					fmt.Println("this violates the rules")
-				}
-			}`),
-		},
-		{
-			description: "Example tests parsed correct when ending with comments",
-			code: []byte(`package foo
-
-			func ExampleFoo() {
-				fmt.Println("hello world")
-
-				// Output: hello world
-			}
-
-			func ExampleBar() {
-				fmt.Println("hello world")
-				// Output: hello world
-			}`),
-		},
-		{
-			description: "whitespaces parsed correctly in case blocks",
-			code: []byte(`package main
-			func main() {
-				switch true {
-				case 1:
-
-					fmt.Println("this is too much...")
-
-				case 2:
-					fmt.Println("this is too much...")
-
-				}
-			}`),
-			expectedErrorStrings: []string{
-				reasonBlockEndsWithWS,
-				"block should not start with a whitespace",
-			},
-		},
-		{
-			description: "whitespaces parsed correctly in case blocks with comments (whitespace NOT found)",
-			code: []byte(`package main
-			func main() {
-				switch true {
-				case 1:
-					// Some leading comment here
-					fmt.Println("this is too much...")
-				case 2:
-					fmt.Println("this is too much...")
-				}
-			}`),
-		},
-		{
-			description: "declarations may never be cuddled",
-			code: []byte(`package main
-
-			func main() {
-				var b = true
-
-				if b {
-					// b is true here
-				}
-				var a = false
-			}`),
-			expectedErrorStrings: []string{reasonNeverCuddleDeclare},
-		},
-		{
-			description: "nested if statements should not be seen as cuddled",
-			code: []byte(`package main
-
-			func main() {
-				if true {
-					if false {
-						if true && false {
-							// Whelp?!
-						}
-					}
-				}
-			}`),
-		},
-		{
-			description: "whitespaces parsed correctly in labeled blocks",
-			code: []byte(`package main
-			func main() {
-				goto end
-				end:
-
-			}`),
-			expectedErrorStrings: []string{
-				reasonBlockEndsWithWS,
-			},
-		},
-	}
-
-	for _, tc := range cases {
-		t.Run(tc.description, func(t *testing.T) {
-			p := NewProcessor()
-			p.process("unit-test", tc.code)
-
-			require.Len(t, p.result, len(tc.expectedErrorStrings), "correct amount of errors found")
-
-			for i := range tc.expectedErrorStrings {
-				assert.Contains(t, p.result[i].Reason, tc.expectedErrorStrings[i], "expected error found")
-			}
-		})
-	}
-}
-
-func TestShouldAddEmptyLines(t *testing.T) {
-	cases := []struct {
-		description          string
-		skip                 bool
-		code                 []byte
-		expectedErrorStrings []string
-	}{
-		{
-			description: "ok to cuddled if statements directly after assignments multiple values",
-			code: []byte(`package main
-
-			func main() {
-				v, err := strconv.Atoi("1")
-				if err != nil {
-					fmt.Println(v)
-				}
-			}`),
-		},
-		{
-			description: "ok to cuddled if statements directly after assignments single value",
-			code: []byte(`package main
-
-			func main() {
-				a := true
-				if a {
-					fmt.Println("I'm OK")
-				}
-			}`),
-		},
-		{
-			description: "ok to cuddled if statements directly after assignments single value, negations",
-			code: []byte(`package main
-
-			func main() {
-				a, b := true, false
-				if !a {
-					fmt.Println("I'm OK")
-				}
-			}`),
-		},
-		{
-			description: "cannot cuddled if assignments not used",
-			code: []byte(`package main
-
-			func main() {
-				err := ProduceError()
-
-				a, b := true, false
-				if err != nil {
-					fmt.Println("I'm OK")
-				}
-			}`),
-			expectedErrorStrings: []string{reasonOnlyCuddleWithUsedAssign},
-		},
-		{
-			description: "cannot cuddled with other things than assignments",
-			code: []byte(`package main
-
-			func main() {
-				if true {
-					fmt.Println("I'm OK")
-				}
-				if false {
-					fmt.Println("I'm OK")
-				}
-			}`),
-			expectedErrorStrings: []string{reasonOnlyCuddleIfWithAssign},
-		},
-		{
-			description: "if statement with multiple assignments above and multiple conditions",
-			code: []byte(`package main
-
-			func main() {
-				c := true
-				fooBar := "string"
-				a, b := true, false
-				if a && b || !c && len(fooBar) > 2 {
-					return false
-				}
-
-				a, b, c := someFunc()
-				if z && len(y) > 0 || 3 == 4 {
-					return true
-				}
-			}`),
-			expectedErrorStrings: []string{"only one cuddle assignment allowed before if statement", reasonOnlyCuddleWithUsedAssign},
-		},
-		{
-			description: "if statement with multiple assignments, at least one used",
-			code: []byte(`package main
-
-			func main() {
-				c := true
-				fooBar := "string"
-
-				a, b := true, false
-				if c && b || !c && len(fooBar) > 2 {
-					return false
-				}
-			}`),
-		},
-		{
-			description: "return can never be cuddleded",
-			code: []byte(`package main
-
-			func main() {
-				var foo = true
-
-				if foo {
-					fmt.Println("return")
-				}
-				return
-			}`),
-			expectedErrorStrings: []string{reasonOnlyCuddle2LineReturn},
-		},
-		{
-			description: "assignments should only be cuddled with assignments (negative)",
-			code: []byte(`package main
-
-			func main() {
-				if true {
-					fmt.Println("this is bad...")
-				}
-				foo := true
-			}`),
-			expectedErrorStrings: []string{reasonAssignsCuddleAssign},
-		},
-		{
-			description: "assignments should only be cuddled with assignments",
-			code: []byte(`package main
-
-			func main() {
-				bar := false
-				foo := true
-				biz := true
-
-				return
-			}`),
-		},
-		{
-			description: "expressions cannot be cuddled with declarations",
-			code: []byte(`package main
-
-			func main() {
-				var a bool
-				fmt.Println(a)
-
-				return
-			}`),
-			expectedErrorStrings: []string{reasonExpressionCuddledWithDeclOrRet},
-		},
-		{
-			description: "expressions can be cuddlede with assignments",
-			code: []byte(`package main
-
-			func main() {
-				a := true
-				fmt.Println(a)
-
-				return
-			}`),
-		},
-		{
-			description: "ranges cannot be cuddled with assignments not used in the range",
-			code: []byte(`package main
-
-			func main() {
-				anotherList := make([]string, 5)
-
-				myList := make([]int, 10)
-				for i := range anotherList {
-					fmt.Println(i)
-				}
-			}`),
-			expectedErrorStrings: []string{reasonRangeCuddledWithoutUse},
-		},
-		{
-			description: "ranges can be cuddled if iteration is of assignment above",
-			code: []byte(`package main
-
-			func main() {
-				myList := make([]string, 5)
-
-				anotherList := make([]int, 10)
-				for i := range anotherList {
-					fmt.Println(i)
-				}
-			}`),
-		},
-		{
-			description: "ranges can be cuddled if multiple assignments and/or values iterated",
-			code: []byte(`package main
-
-			func main() {
-				someList, anotherList := GetTwoListsOfStrings()
-				for i := range append(someList, anotherList...) {
-					fmt.Println(i)
-				}
-
-				aThirdList := GetList()
-				for i := range append(someList, aThirdList...) {
-					fmt.Println(i)
-				}
-			}`),
-		},
-		{
-			description: "allow cuddle for immediate assignment in block",
-			code: []byte(`package main
-
-			func main() {
-				// This should be allowed
-				idx := i
-				if i > 0 {
-					idx = i - 1
-				}
-
-				vals := map[int]struct{}{}
-				for i := range make([]int, 5) {
-					vals[i] = struct{}{}
-				}
-
-				// This last issue fails.
-				x := []int{}
-
-				vals := map[int]struct{}{}
-				for i := range make([]int, 5) {
-					x = append(x, i)
-				}
-			}`),
-			expectedErrorStrings: []string{reasonRangeCuddledWithoutUse},
-		},
-		{
-			description: "can cuddle only one assignment",
-			code: []byte(`package main
-
-			func main() {
-				// This is allowed
-				foo := true
-				bar := false
-
-				biz := true || false
-				if biz {
-					return true
-				}
-
-				// And this is allowed
-
-				foo := true
-				bar := false
-				biz := true || false
-
-				if biz {
-					return true
-				}
-
-				// But not this
-
-				foo := true
-				bar := false
-				biz := true || false
-				if biz {
-					return false
-				}
-			}`),
-			expectedErrorStrings: []string{"only one cuddle assignment allowed before if statement"},
-		},
-		{
-			description: "using identifies with indicies",
-			code: []byte(`package main
-
-			func main() {
-				// This should be OK
-				runes := []rune{'+', '-'}
-				if runes[0] == '+' || runes[0] == '-' {
-					return string(runes[1:])
-				}
-
-				// And this
-				listTwo := []string{}
-
-				listOne := []string{"one"}
-				if listOne[0] == "two" {
-					return "not allowed"
-				}
-			}`),
-		},
-		{
-			description: "simple defer statements",
-			code: []byte(`package main
-
-			func main() {
-				// OK
-				thingOne := getOne()
-				thingTwo := getTwo()
-
-				defer thingOne.Close()
-				defer thingTwo.Close()
-
-				// OK
-				thingOne := getOne()
-				defer thingOne.Close()
-
-				thingTwo := getTwo()
-				defer thingTwo.Close()
-
-				// NOT OK
-				thingOne := getOne()
-				defer thingOne.Close()
-				thingTwo := getTwo()
-				defer thingTwo.Close()
-
-				// NOT OK
-				thingOne := getOne()
-				thingTwo := getTwo()
-				defer thingOne.Close()
-				defer thingTwo.Close()
-			}`),
-			expectedErrorStrings: []string{
-				reasonAssignsCuddleAssign,
-				reasonOneCuddleBeforeDefer,
-				reasonOneCuddleBeforeDefer,
-			},
-		},
-		{
-			description: "defer statements can be cuddled",
-			code: []byte(`package main
-
-			import "sync"
-
-			func main() {
-				m := sync.Mutex{}
-
-				// This should probably be OK
-				m.Lock()
-				defer m.Unlock()
-
-				foo := true
-				defer func(b bool) {
-					fmt.Printf("%v", b)
-				}()
-			}`),
-			expectedErrorStrings: []string{
-				reasonDeferCuddledWithOtherVar,
-			},
-		},
-		{
-			description: "selector expressions are handled like variables",
-			code: []byte(`package main
-
-			type T struct {
-				List []string
-			}
-
-			func main() {
-				t := makeT()
-				for _, x := range t.List {
-					return "this is not like a variable"
-				}
-			}
-
-			func makeT() T {
-				return T{
-					List: []string{"one", "two"},
-				}
-			}`),
-		},
-		{
-			description: "return statements may be cuddled if the block is less than three lines",
-			code: []byte(`package main
-
-			type T struct {
-				Name string
-				X	 bool
-			}
-
-			func main() {
-				t := &T{"someT"}
-
-				t.SetX()
-			}
-
-			func (t *T) SetX() *T {
-				t.X = true
-				return t
-			}`),
-			expectedErrorStrings: []string{},
-		},
-		{
-			description: "handle ForStmt",
-			code: []byte(`package main
-
-			func main() {
-				bool := true
-				for {
-					fmt.Println("should not be allowed")
-
-					if bool {
-						break
-					}
-				}
-			}`),
-			expectedErrorStrings: []string{reasonForWithoutCondition},
-		},
-		{
-			description: "support usage if chained",
-			code: []byte(`package main
-
-			func main() {
-				go func() {
-					panic("is this real life?")
-				}()
-
-				fooFunc := func () {}
-				go fooFunc()
-
-				barFunc := func () {}
-				go fooFunc()
-
-				go func() {
-					fmt.Println("hey")
-				}()
-
-				cuddled := true
-				go func() {
-					fmt.Println("hey")
-				}()
-			}`),
-			expectedErrorStrings: []string{
-				reasonGoFuncWithoutAssign,
-				reasonGoFuncWithoutAssign,
-			},
-		},
-		{
-			description: "switch statements",
-			code: []byte(`package main
-
-			func main() {
-				// OK
-				var b bool
-				switch b {
-				case true:
-					return "a"
-				case false:
-					return "b"
-				}
-
-				// OK
-				t := time.Now()
-
-				switch {
-				case t.Hour() < 12:
-					fmt.Println("It's before noon")
-				default:
-					fmt.Println("It's after noon")
-				}
-
-				// Not ok
-				var b bool
-				switch anotherBool {
-				case true:
-					return "a"
-				case false:
-					return "b"
-				}
-
-				// Not ok
-				t := time.Now()
-				switch {
-				case t.Hour() < 12:
-					fmt.Println("It's before noon")
-				default:
-					fmt.Println("It's after noon")
-				}
-			}`),
-			expectedErrorStrings: []string{
-				reasonSwitchCuddledWithoutUse,
-				reasonAnonSwitchCuddled,
-			},
-		},
-		{
-			description: "type switch statements",
-			code: []byte(`package main
-
-			func main() {
-				// Ok!
-				x := GetSome()
-				switch v := x.(type) {
-				case int:
-					return "got int"
-				default:
-					return "got other"
-				}
-
-				// Ok?
-				var id string
-				switch i := objectID.(type) {
-				case int:
-					id = strconv.Itoa(i)
-				case uint32:
-					id = strconv.Itoa(int(i))
-				case string:
-					id = i
-				}
-
-				// Not ok
-				var b bool
-				switch AnotherVal.(type) {
-				case int:
-					return "a"
-				case string:
-					return "b"
-				}
-			}`),
-			expectedErrorStrings: []string{
-				reasonTypeSwitchCuddledWithoutUse,
-			},
-		},
-		{
-			description: "only cuddle append if appended",
-			code: []byte(`package main
-
-			func main() {
-				var (
-					someList = []string{}
-				)
-
-				// Should be OK
-				bar := "baz"
-				someList = append(someList, bar)
-
-				// But not this
-				bar := "baz"
-				someList = append(someList, "notBar")
-			}`),
-			expectedErrorStrings: []string{reasonAppendCuddledWithoutUse},
-		},
-		{
-			description: "cuddle expressions to assignments",
-			code: []byte(`package main
-
-			func main() {
-				// This should be OK
-				foo := true
-				someFunc(foo)
-
-				// And not this
-				foo := true
-				someFunc(false)
-			}`),
-			expectedErrorStrings: []string{reasonExprCuddlingNonAssignedVar},
-		},
-		{
-			description: "channels and select, no false positives",
-			code: []byte(`package main
-
-			func main() {
-				timeoutCh := time.After(timeout)
-
-				for range make([]int, 10) {
-					select {
-					case <-timeoutCh:
-						return true
-					case <-time.After(10 * time.Millisecond):
-						return false
-					}
-				}
-			}`),
-		},
-		{
-			description: "switch statements",
-			code: []byte(`package main
-
-			func main() {
-				t := GetT()
-				switch t.GetField() {
-				case 1:
-					return 0
-				case 2:
-					return 1
-				}
-
-				notT := GetX()
-				switch notT {
-				case "x":
-					return 0
-				}
-			}`),
-			expectedErrorStrings: []string{},
-		},
-		{
-			description: "select statements",
-			code: []byte(`package main
-
-			func main() {
-				select {
-				case <-time.After(1*time.Second):
-					return "1s"
-				default:
-					return "are we there yet?"
-				}
-			}`),
-		},
-		{
-			description: "branch statements (continue/break)",
-			code: []byte(`package main
-
-			func main() {
-				for {
-					// Allowed if only one statement in block.
-					if true {
-						singleLine := true
-						break
-					}
-
-					// Not allowed for multiple lines
-					if true && false {
-						multiLine := true
-						maybeVar := "var"
-						continue
-					}
-
-					// Multiple lines require newline
-					if false {
-						multiLine := true
-						maybeVar := "var"
-
-						break
-					}
-				}
-			}`),
-			expectedErrorStrings: []string{
-				"branch statements should not be cuddled if block has more than two lines",
-			},
-		},
-		{
-			description: "append",
-			code: []byte(`package main
-
-			func main() {
-				var (
-					someList = []string{}
-				)
-
-				// Should be OK
-				bar := "baz"
-				someList = append(someList, fmt.Sprintf("%s", bar))
-
-				// Should be OK
-				bar := "baz"
-				someList = append(someList, []string{"foo", bar}...)
-
-				// Should be OK
-				bar := "baz"
-				someList = append(someList, bar)
-
-				// But not this
-				bar := "baz"
-				someList = append(someList, "notBar")
-
-				// Ok
-				biz := "str"
-				whatever := ItsJustAppend(biz)
-
-				// Ok
-				zzz := "str"
-				whatever := SoThisIsOK(biz)
-			}`),
-			expectedErrorStrings: []string{
-				reasonAppendCuddledWithoutUse,
-			},
-		},
-		{
-			description: "support usage if chained",
-			code: []byte(`package main
-
-			func main() {
-				r := map[string]interface{}{}
-				if err := json.NewDecoder(someReader).Decode(&r); err != nil {
-					return "this should be OK"
-				}
-			}`),
-		},
-		{
-			description: "support function literals",
-			code: []byte(`package main
-
-			func main() {
-				n := 1
-				f := func() {
-
-					// This violates whitespaces
-
-					// Violates cuddle
-					notThis := false
-					if isThisTested {
-						return
-
-					}
-				}
+func TestDefaultConfig(t *testing.T) {
+	testdata := analysistest.TestData()
+	analyzer := Analyzer
+	analyzer.Flags = flags()
 
-				f()
-			}`),
-			expectedErrorStrings: []string{
-				"block should not start with a whitespace",
-				reasonBlockEndsWithWS,
-				reasonOnlyCuddleWithUsedAssign,
-			},
-		},
-		{
-			description: "locks",
-			code: []byte(`package main
-
-			func main() {
-				hashFileCache.Lock()
-				out, ok := hashFileCache.m[file]
-				hashFileCache.Unlock()
-
-				mu := &sync.Mutex{}
-				mu.X(y).Z.RLock()
-				x, y := someMap[someKey]
-				mu.RUnlock()
-			}`),
-		},
-		{
-			description: "append type",
-			code: []byte(`package main
-
-			func main() {
-				s := []string{}
-
-				// Multiple append should be OK
-				s = append(s, "one")
-				s = append(s, "two")
-				s = append(s, "three")
-
-				// Both assigned and called should be allowed to be cuddled.
-				// It's not nice, but they belong'
-				p.defs = append(p.defs, x)
-				def.parseFrom(p)
-				p.defs = append(p.defs, def)
-				def.parseFrom(p)
-				def.parseFrom(p)
-				p.defs = append(p.defs, x)
-			}`),
-		},
-		{
-			description: "AllowAssignAndCallCuddle",
-			code: []byte(`package main
-
-			func main() {
-				for i := range make([]int, 10) {
-					fmt.Println("x")
-				}
-				x.Calling()
-			}`),
-			expectedErrorStrings: []string{
-				reasonExpressionCuddledWithBlock,
-			},
-		},
-		{
-			description: "indexes in maps and arrays",
-			code: []byte(`package main
-
-			func main() {
-				for i := range make([]int, 10) {
-					key := GetKey()
-					if val, ok := someMap[key]; ok {
-						fmt.Println("ok!")
-					}
-
-					someOtherMap := GetMap()
-					if val, ok := someOtherMap[key]; ok {
-						fmt.Println("ok")
-					}
-
-					someIndex := 3
-					if val := someSlice[someIndex]; val != nil {
-						retunr
-					}
-				}
-			}`),
-		},
-		{
-			description: "splice slice, concat key",
-			code: []byte(`package main
-
-			func main() {
-				start := 0
-				if v := aSlice[start:3]; v {
-					fmt.Println("")
-				}
-
-				someKey := "str"
-				if v, ok := someMap[obtain(someKey)+"str"]; ok {
-					fmt.Println("Hey there")
-				}
-
-				end := 10
-				if v := arr[3:notEnd]; !v {
-					// Error
-				}
-
-				notKey := "str"
-				if v, ok := someMap[someKey]; ok {
-					// Error
-				}
-			}`),
-			expectedErrorStrings: []string{
-				reasonOnlyCuddleWithUsedAssign,
-				reasonOnlyCuddleWithUsedAssign,
-			},
-		},
-		{
-			description: "multiline case statements",
-			code: []byte(`package main
-
-			func main() {
-				switch {
-				case true,
-					false:
-					fmt.Println("ok")
-				case true ||
-					false:
-					fmt.Println("ok")
-				case true, false:
-					fmt.Println("ok")
-				case true || false:
-					fmt.Println("ok")
-				case true,
-					false:
-
-					fmt.Println("starting whitespace multiline case")
-				case true ||
-					false:
-
-					fmt.Println("starting whitespace multiline case")
-				case true,
-					false:
-					fmt.Println("ending whitespace multiline case")
-
-				case true,
-					false:
-					fmt.Println("last case should also fail")
-
-				}
-			}`),
-			expectedErrorStrings: []string{
-				reasonBlockEndsWithWS,
-				reasonBlockStartsWithWS,
-				reasonBlockStartsWithWS,
-			},
-		},
-		{
-			description: "allow http body close best practice",
-			code: []byte(`package main
-
-			func main() {
-				resp, err := client.Do(req)
-				if err != nil {
-					return err
-				}
-				defer resp.Body.Close()
-			}`),
-		},
-		{
-			description: "multiple go statements can be cuddled",
-			code: []byte(`package main
-
-			func main() {
-				t1 := NewT()
-				t2 := NewT()
-				t3 := NewT()
-
-				go t1()
-				go t2()
-				go t3()
-
-				multiCuddle1 := NewT()
-				multiCuddle2 := NewT()
-				go multiCuddle2()
-
-				multiCuddle3 := NeT()
-				go multiCuddle1()
-
-				t4 := NewT()
-				t5 := NewT()
-				go t5()
-				go t4()
-			}`),
-			expectedErrorStrings: []string{
-				reasonOneCuddleBeforeGo,
-				reasonGoFuncWithoutAssign,
-				reasonOneCuddleBeforeGo,
-			},
-		},
-		{
-			description: "boilerplate",
-			code: []byte(`package main
-
-			func main() {
-				counter := 0
-				if somethingTrue {
-					counter++
-				}
-
-				counterTwo := 0
-				if somethingTrue {
-					counterTwo--
-				}
-
-				notCounter := 0
-				if somethingTrue {
-					counter--
-				}
-			}`),
-			expectedErrorStrings: []string{
-				reasonOnlyCuddleWithUsedAssign,
-			},
-		},
-		{
-			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{
-				reasonAssignsCuddleAssign,
-				reasonAssignsCuddleAssign,
-				reasonAssignsCuddleAssign,
-				reasonAssignsCuddleAssign,
-				reasonAssignsCuddleAssign,
-				reasonAssignsCuddleAssign,
-				reasonAssignsCuddleAssign,
-				reasonAssignsCuddleAssign,
-			},
-		},
-		{
-			description: "should allow defer func literals just like any block",
-			code: []byte(`package main
-
-			func main() {
-				tx := BeginTx(db)
-				defer func() {
-					EndTx(tx, err)
-				}()
-
-				i := 100
-				defer func() {
-					i = 0
-				}()
-			}`),
-		},
-		{
-			description: "go stmt can be cuddled if used in call",
-			code: []byte(`package main
-
-			func main() {
-				wg := sync.WaitGroup{}
-				go func(wg) {
-					defer wg.Done()
-
-					fmt.Println("cuddling correctly")
-				}()
-
-				one := 1
-				go func(wg) {
-					defer wg.Done()
-
-					fmt.Println("cuddling faulty")
-				}()
-
-				two := 2
-				go someFunc(two)
-
-				wg.Wait()
-
-				_ = one
-				_ = two
-			}`),
-			expectedErrorStrings: []string{"go statements can only invoke functions assigned on line above"},
-		},
-		{
-			description: "calling go stmt on type used above should be ok",
-			code: []byte(`package main
-
-			type A struct {
-				wg sync.WaitGrop
-			}
-
-			func (a *A) doWork() {
-				fmt.Println("doing work")
-				a.wg.Done()
-			}
-
-			func main() {
-				a := A{
-					wg: sync.WaitGroup{},
-				}
-
-				a.wg.Add(1)
-				go a.doWork()
-
-				wg.Done()
-			}`),
-		},
-	}
-
-	for _, tc := range cases {
-		t.Run(tc.description, func(t *testing.T) {
-			if tc.skip {
-				t.Skip("not implemented")
-			}
-
-			p := NewProcessor()
-			p.process("unit-test", tc.code)
-
-			require.Len(t, p.result, len(tc.expectedErrorStrings), "correct amount of errors found")
-
-			for i := range tc.expectedErrorStrings {
-				assert.Contains(t, p.result[i].Reason, tc.expectedErrorStrings[i], "expected error found")
-			}
-		})
-	}
+	analysistest.RunWithSuggestedFixes(t, testdata, analyzer, "default_config")
 }
 
 func TestWithConfig(t *testing.T) {
-	// This test is used to simulate code to perform TDD. This part should never
-	// be committed with any test.
-	cases := []struct {
-		description          string
-		code                 []byte
-		expectedErrorStrings []string
-		customConfig         *Configuration
-	}{
-		{
-			description: "AllowAssignAndCallCuddle",
-			code: []byte(`package main
-
-			func main() {
-				p.token(':')
-				d.StartBit = p.uint()
-				p.token('|')
-				d.Size = p.uint()
-			}`),
-			customConfig: &Configuration{
-				AllowAssignAndCallCuddle: true,
-			},
-		},
-		{
-			description: "multi line assignment ok when enabled",
-			code: []byte(`package main
-
-			func main() {
-				// This should not be OK since the assignment is split at
-				// multiple rows.
-				err := SomeFunc(
-					"taking multiple values",
-					"suddendly not a single line",
-				)
-				if err != nil {
-					fmt.Println("denied")
-				}
-			}`),
-			customConfig: &Configuration{
-				AllowMultiLineAssignCuddle: false,
-			},
-			expectedErrorStrings: []string{reasonOnlyCuddleIfWithAssign},
-		},
-		{
-			description: "multi line assignment not ok when disabled",
-			code: []byte(`package main
-
-			func main() {
-				// This should not be OK since the assignment is split at
-				// multiple rows.
-				err := SomeFunc(
-					"taking multiple values",
-					"suddenly not a single line",
-				)
-				if err != nil {
-					fmt.Println("accepted")
-				}
-
-				// This should still fail since it's not the same variable
-				noErr := SomeFunc(
-					"taking multiple values",
-					"suddenly not a single line",
-				)
-				if err != nil {
-					fmt.Println("rejected")
-				}
-			}`),
-			customConfig: &Configuration{
-				AllowMultiLineAssignCuddle: true,
-			},
-			expectedErrorStrings: []string{reasonOnlyCuddleWithUsedAssign},
-		},
-		{
-			description: "strict append",
-			code: []byte(`package main
-
-			func main() {
-				x := []string{}
-				y := "not in x"
-				x = append(x, "not y") // Should not be allowed
-
-				z := "in x"
-				x = append(x, z) // Should be allowed
-
-				m := transform(z)
-				x := append(x, z) // Should be allowed
-			}`),
-			customConfig: &Configuration{
-				StrictAppend: true,
-			},
-			expectedErrorStrings: []string{reasonAppendCuddledWithoutUse},
-		},
-		{
-			description: "allow cuddle var",
-			code: []byte(`package main
-
-			func main() {
-				var t bool
-				var err error
-
-				var t = true
-				if t {
-					fmt.Println("x")
-				}
-			}`),
-			customConfig: &Configuration{
-				AllowCuddleDeclaration: true,
-			},
-		},
-		{
-			description: "support to end blocks with a comment",
-			code: []byte(`package main
-
-			func main() {
-				for i := range make([]int, 10) {
-					fmt.Println("x")
-					// This is OK
-				}
-
-				for i := range make([]int, 10) {
-					fmt.Println("x")
-					// Pad
-					// With empty lines
-					//
-					// This is OK
-				}
-
-				for i := range make([]int, 10) {
-					fmt.Println("x")
-
-					// This is NOT OK
-				}
+	defaultConfig := config
 
-				for i := range make([]int, 10) {
-					fmt.Println("x")
-					// This is NOT OK
+	flags := flag.NewFlagSet("", flag.ExitOnError)
 
-				}
+	testdata := analysistest.TestData()
+	analyzer := Analyzer
+	analyzer.Flags = *flags
 
-				for i := range make([]int, 10) {
-					fmt.Println("x")
-					/* Block comment one line OK */
-				}
-
-				for i := range make([]int, 10) {
-					fmt.Println("x")
-					/*
-						Block comment one multiple lines OK
-					*/
-				}
-
-				switch {
-				case true:
-					fmt.Println("x")
-					// This is OK
-				case false:
-					fmt.Println("y")
-					// This is OK
-				}
-			}`),
-			customConfig: &Configuration{
-				AllowTrailingComment: true,
-			},
-			expectedErrorStrings: []string{
-				reasonBlockEndsWithWS,
-				reasonBlockEndsWithWS,
-			},
-		},
+	for _, tc := range []struct {
+		subdir   string
+		configFn func()
+	}{
 		{
-			description: "case blocks can end with or without newlines and comments",
-			code: []byte(`package main
-
-			func main() {
-				switch "x" {
-				case "a correct": // Test
-					fmt.Println("1")
-					fmt.Println("1")
-				case "a correct":
-					fmt.Println("1")
-					fmt.Println("2")
-
-				case "b correct":
-					fmt.Println("1")
-				case "b correct":
-					fmt.Println("1")
-
-				case "b bad":
-					fmt.Println("1")
-					// I'm not allowed'
-				case "b bad":
-					fmt.Println("1")
-					// I'm not allowed'
-
-				case "no false positives for if":
-					fmt.Println("checking")
-
-					// Some comment
-					if 1 < 2 {
-						// Another comment
-						if 2 > 1 {
-							fmt.Println("really sure")
-						}
-					}
-				case "end": // This is a case
-					fmt.Println("4")
-				}
-			}`),
-			customConfig: &Configuration{
-				ForceCaseTrailingWhitespaceLimit: 0,
-				AllowTrailingComment:             false,
+			subdir: "case_blocks",
+			configFn: func() {
+				config.ForceCaseTrailingWhitespaceLimit = 3
 			},
 		},
 		{
-			description: "enforce newline at size 3",
-			code: []byte(`package main
-
-			func main() {
-				switch "x" {
-				case "a correct": // Test
-					fmt.Println("1")
-				case "a ok":
-					fmt.Println("1")
-
-				case "b correct":
-					fmt.Println("1")
-					fmt.Println("1")
-					fmt.Println("1")
-
-				case "b bad":
-					fmt.Println("1")
-					fmt.Println("1")
-					fmt.Println("1")
-				case "b correct":
-					fmt.Println("1")
-					fmt.Println("1")
-					fmt.Println("1")
-					// This is OK
-
-				case "b bad":
-					fmt.Println("1")
-					fmt.Println("1")
-					fmt.Println("1")
-					// This is not OK, no whitespace
-				case "b ok":
-					fmt.Println("1")
-					fmt.Println("1")
-					fmt.Println("1")
-					/*
-						This is not OK
-						Multiline but no whitespace
-					*/
-				case "end": // This is a case
-					fmt.Println("4")
-				}
-			}`),
-			customConfig: &Configuration{
-				ForceCaseTrailingWhitespaceLimit: 3,
-				AllowTrailingComment:             true,
-			},
-			expectedErrorStrings: []string{
-				reasonCaseBlockTooCuddly,
-				reasonCaseBlockTooCuddly,
-				reasonCaseBlockTooCuddly,
+			subdir: "multi_line_assign",
+			configFn: func() {
+				config.AllowMultiLineAssignCuddle = false
 			},
 		},
 		{
-			description: "must cuddle error checks with the error assignment",
-			customConfig: &Configuration{
-				ForceCuddleErrCheckAndAssign: true,
-				ErrorVariableNames:           []string{"err"},
+			subdir: "assign_and_call",
+			configFn: func() {
+				config.AllowAssignAndCallCuddle = false
 			},
-			code: []byte(`package main
-
-			import "errors"
-
-			func main() {
-				err := errors.New("bad thing")
-
-				if err != nil {
-					fmt.Println("I'm OK")
-				}
-			}`),
-			expectedErrorStrings: []string{reasonMustCuddleErrCheck},
 		},
 		{
-			description: "must cuddle error checks with the error assignment multivalue",
-			customConfig: &Configuration{
-				AllowMultiLineAssignCuddle:   true,
-				ForceCuddleErrCheckAndAssign: true,
-				ErrorVariableNames:           []string{"err"},
+			subdir: "trailing_comments",
+			configFn: func() {
+				config.AllowTrailingComment = true
 			},
-			code: []byte(`package main
-
-			import "errors"
-
-			func main() {
-				b, err := FSByte(useLocal, name)
-
-				if err != nil {
-					panic(err)
-				}
-			}`),
-			expectedErrorStrings: []string{reasonMustCuddleErrCheck},
 		},
 		{
-			description: "must cuddle error checks with the error assignment only on assignment",
-			customConfig: &Configuration{
-				ForceCuddleErrCheckAndAssign: true,
-				ErrorVariableNames:           []string{"err"},
+			subdir: "separate_leading_whitespace",
+			configFn: func() {
+				config.AllowSeparatedLeadingComment = true
 			},
-			code: []byte(`package main
-
-			import "errors"
-
-			func main() {
-				var (
-					err error
-					once sync.Once
-				)
-
-				once.Do(func() {
-					err = ProduceError()
-				})
-
-				if err != nil {
-					return nil, err
-				}
-			}`),
-			expectedErrorStrings: []string{},
 		},
 		{
-			description: "must cuddle error checks with the error assignment multivalue NoError",
-			customConfig: &Configuration{
-				AllowMultiLineAssignCuddle:   true,
-				ForceCuddleErrCheckAndAssign: true,
-				ErrorVariableNames:           []string{"err"},
+			subdir: "error_check",
+			configFn: func() {
+				config.ForceCuddleErrCheckAndAssign = true
 			},
-			code: []byte(`package main
-
-			import "errors"
-
-			func main() {
-				b, err := FSByte(useLocal, name)
-				if err != nil {
-					panic(err)
-				}
-			}`),
-			expectedErrorStrings: []string{},
 		},
 		{
-			description: "must cuddle error checks with the error assignment known err",
-			customConfig: &Configuration{
-				AllowMultiLineAssignCuddle:   true,
-				ForceCuddleErrCheckAndAssign: true,
-				ErrorVariableNames:           []string{"err"},
+			subdir: "short_decl",
+			configFn: func() {
+				config.ForceExclusiveShortDeclarations = true
 			},
-			code: []byte(`package main
-
-			import "errors"
-
-			func main() {
-				result, err := FetchSomething()
-
-				if err == sql.ErrNoRows {
-					return []Model{}
-				}
-			}`),
-			expectedErrorStrings: []string{reasonMustCuddleErrCheck},
 		},
 		{
-			description: "err-check cuddle enforcement doesn't generate false-positives.",
-			customConfig: &Configuration{
-				AllowMultiLineAssignCuddle:   true,
-				ForceCuddleErrCheckAndAssign: true,
-				ErrorVariableNames:           []string{"err"},
+			subdir: "strict_append",
+			configFn: func() {
+				config.StrictAppend = false
 			},
-			code: []byte(`package main
-
-			import "errors"
-
-			func main() {
-				result, err := FetchSomething()
-				if err == sql.ErrNoRows {
-					return []Model{}
-				}
-
-				foo := generateFoo()
-				if foo == "bar" {
- 				    handleBar()
-                }
-
-				var baz []string
-
-				err = loadStuff(&baz)
-				if err != nil{
-					return nil
-				}
-
-				if err := ProduceError(); err != nil {
-					return err
-				}
-
-				return baz
-			}`),
-			expectedErrorStrings: []string{},
 		},
 		{
-			description: "err-check cuddle enforcement multiline",
-			customConfig: &Configuration{
-				AllowAssignAndCallCuddle:     false,
-				ForceCuddleErrCheckAndAssign: true,
-				ErrorVariableNames:           []string{"err"},
+			subdir: "assign_and_anything",
+			configFn: func() {
+				config.AllowAssignAndAnythingCuddle = true
 			},
-			code: []byte(`package main
-
-			import "errors"
-
-			func main() {
-				err := db.
-					Where("foo = ?", bar).
-					Preload("More").
-					Find(&x).
-					Error
-
-				if err != nil {
-					panic(err)
-				}
-			}`),
-			expectedErrorStrings: []string{},
 		},
 		{
-			description: "allow separated leading comment",
-			customConfig: &Configuration{
-				AllowSeparatedLeadingComment: true,
+			subdir: "decl",
+			configFn: func() {
+				config.AllowCuddleDeclaration = true
 			},
-			code: []byte(`package main
-
-			func main() {
-				// These blocks should not generate error
-				func () {
-					// Comment
-
-					// Comment
-					fmt.Println("Hello, World")
-				}
-
-				func () {
-					/*
-						Multiline
-					*/
-
-					/*
-						Multiline
-					*/
-					fmt.Println("Hello, World")
-				}
-
-				func () {
-					/*
-						Multiline
-					*/
-
-					// Comment
-					fmt.Println("Hello, World")
-				}
-
-				func () {
-					// Comment
-
-					/*
-						Multiline
-					*/
-					fmt.Println("Hello, World")
-				}
-
-				func () { // Comment
-					/*
-						Multiline
-					*/
-					fmt.Println("Hello, World")
-				}
-
-				func () { // Comment
-					// and one more
-
-					fmt.Println("Hello, World")
-				}
-
-				func () { // Comment
-					fmt.Println("Hello, World")
-				}
-			}`),
 		},
-		{
-			description: "allow separated leading comment (negative)",
-			customConfig: &Configuration{
-				AllowSeparatedLeadingComment: true,
-			},
-			code: []byte(`package main
-
-			func main() {
-				// These blocks should generate error
-				func () {
-
-					// Comment
-
-					// Comment
-					fmt.Println("Hello, World")
-				}
-
-				func () {
-
-					fmt.Println("Hello, World")
-				}
-
-				func () {
-					// Comment
-
-
-					fmt.Println("Hello, World")
-				}
-
-				func () {
-					// Comment
-
-					// Comment
-
-
-					fmt.Println("Hello, World")
-				}
-
-				func () { // Comment
-
-					/*
-						Multiline
-					*/
-					fmt.Println("Hello, World")
-				}
-			}`),
-			expectedErrorStrings: []string{
-				reasonBlockStartsWithWS,
-				reasonBlockStartsWithWS,
-				reasonBlockStartsWithWS,
-				reasonBlockStartsWithWS,
-				reasonBlockStartsWithWS,
-			},
-		},
-		{
-			description: "only warn about cuddling errors if it's an expression above",
-			customConfig: &Configuration{
-				ForceCuddleErrCheckAndAssign: true,
-				ErrorVariableNames:           []string{"err"},
-			},
-			code: []byte(`package main
-
-			var ErrSomething error
-
-			func validateErr(err error) {
-				if err == nil {
-					return
-				}
-
-				if errors.Is(err, ErrSomething) {
-					return
-				}
-
-				// Should be valid since the if actually is cuddled so the error
-				// check assumes something right is going on here. If
-				// anotherThingToCheck wasn't used in the if statement we would
-				// get a regular if cuddle error.
-				anotherThingToCheck := someFunc()
-				if multiCheck(anotherThingToCheck, err) {
-					fmt.Println("this must be OK, err is not assigned above")
-				}
-
-				notUsedInNextIf := someFunc()
-
-				if multiCheck(anotherThingToCheck, err) {
-					fmt.Println("this should not warn, we didn't assign err above")
-				}
-
-				// This fails under the cuddle if rule.
-				if err != nil {
-					return
-				}
-				if !errors.Is(err, ErrSomething) {
-					return
-				}
-			}`),
-			expectedErrorStrings: []string{reasonOnlyCuddleIfWithAssign},
-		},
-		{
-			description: "allow cuddling of declaration (#83)",
-			code: []byte(`package main
-
-			func main() {
-				var first = 1
-				var second = 2
-				third := 3
-
-				var config Configuration
-				if err := conf.Load(config); err != nil {
-					panic(err)
-				}
-
-				var config Configuration
-				conf.Load(&config)
-
-				var config Configuration
-				err = json.Unmarshal(body, &config)
-
-				var notUsed bool
-				err = json.Unmarshal(body, &something)
-
-				var x = 1
-				if x > 0 {
-					// ...
-				}
-
-				// This one fails because we're not using y in the if statement.
-				var y = 1
-				if first > 0 {
-					// ...
-				}
-
-				// This one fails because we cuddled too many
-				var z = 1
-				var zz = 2
-				if zz > z {
-					// ...
-				}
-			}`),
-			customConfig: &Configuration{
-				AllowAssignAndCallCuddle: true,
-				AllowCuddleDeclaration:   true,
-			},
-			expectedErrorStrings: []string{
-				reasonOnlyCuddleWithUsedAssign,
-				reasonOnlyOneCuddle,
-			},
-		},
-		{
-			description: "force short decls to cuddle alone",
-			code: []byte(`package main
-
-			func main() {
-				// OK
-				a := 1
-				b := 2
-
-				// should fail
-				c := 3
-				b = 2
-
-				// should fail
-				err := DoSomething()
-				if err != nil {
-					b = 4
-				}
-			}`),
-			customConfig: &Configuration{
-				AllowAssignAndAnythingCuddle:    true,
-				ForceCuddleErrCheckAndAssign:    true,
-				ForceExclusiveShortDeclarations: true,
-			},
-			expectedErrorStrings: []string{
-				reasonShortDeclNotExclusive,
-				reasonShortDeclNotExclusive,
-			},
-		},
-		{
-			description: "key value pairs can use variables",
-			code: []byte(`package main
-
-			func main() {
-				someData := GetSomeData()
-				log.WithFields(log.Fields{
-					"data1": someData.One,
-					"data2": someData.Two,
-					"data3": someData.Three,
-				}).Debug("Got some data")
-			}`),
-		},
-	}
-
-	for _, tc := range cases {
-		t.Run(tc.description, func(t *testing.T) {
-			p := NewProcessor()
-			if tc.customConfig != nil {
-				p = NewProcessorWithConfig(*tc.customConfig)
-			}
-
-			p.process("unit-test", tc.code)
-			require.Len(t, p.result, len(tc.expectedErrorStrings), "correct amount of errors found")
-
-			for i := range tc.expectedErrorStrings {
-				assert.Contains(t, p.result[i].Reason, tc.expectedErrorStrings[i], "expected error found")
-			}
-		})
-	}
-}
-
-func TestTODO(t *testing.T) {
-	// This test is used to simulate code to perform TDD. This part should never
-	// be committed with any test.
-	cases := []struct {
-		description          string
-		code                 []byte
-		expectedErrorStrings []string
-		customConfig         *Configuration
-	}{
-		{
-			description: "boilerplate",
-			code: []byte(`package main
-
-			func main() {
-				for i := range make([]int, 10) {
-					fmt.Println("x")
-				}
-			}`),
-		},
-	}
-
-	for _, tc := range cases {
-		t.Run(tc.description, func(t *testing.T) {
-			p := NewProcessor()
-			if tc.customConfig != nil {
-				p = NewProcessorWithConfig(*tc.customConfig)
-			}
-
-			p.process("unit-test", tc.code)
-
-			t.Logf("WARNINGS: %s", p.warnings)
-
-			for _, r := range p.result {
-				fmt.Println(r.String())
-			}
-
-			require.Len(t, p.result, len(tc.expectedErrorStrings), "correct amount of errors found")
+	} {
+		t.Run(tc.subdir, func(t *testing.T) {
+			config = defaultConfig
+			tc.configFn()
 
-			for i := range tc.expectedErrorStrings {
-				assert.Contains(t, p.result[i].Reason, tc.expectedErrorStrings[i], "expected error found")
-			}
+			analysistest.RunWithSuggestedFixes(t, testdata, analyzer, filepath.Join("with_config", tc.subdir))
 		})
 	}
 }