Skip to content
Merged
81 changes: 81 additions & 0 deletions CHECKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
- [`inc-dec`](#inc-dec)
- [`label`](#label)
- [`leading-whitespace`](#leading-whitespace)
- [`newline-after-block`](#newline-after-block)
- [`range`](#range)
- [`return`](#return)
- [`select`](#select)
Expand All @@ -28,6 +29,7 @@
- [Configuration](#configuration)
- [`allow-first-in-block`](#allow-first-in-block)
- [`allow-whole-block`](#allow-whole-block)
- [`case-max-lines`](#case-max-lines)

## Checks

Expand Down Expand Up @@ -1282,6 +1284,68 @@ if true {

</tbody></table>

### `newline-after-block`

Block statements (`if`, `for`, `switch`, etc.) should be followed by a blank
line to visually separate them from subsequent code.

> **NOTE** An exception is made for `defer` statements that follow an
> `if err != nil` block when the defer references a variable assigned on the
> line above the if statement. This is a common pattern for resource cleanup.

<table>
<thead><tr><th>Bad</th><th>Good</th></tr></thead>
<tbody>
<tr><td valign="top">

```go
if true {
fmt.Println("hello")
}
fmt.Println("world") // 1

for i := 0; i < 3; i++ {
fmt.Println(i)
}
x := 1 // 2
```

</td><td valign="top">

```go
if true {
fmt.Println("hello")
}

fmt.Println("world")

for i := 0; i < 3; i++ {
fmt.Println(i)
}

x := 1

// Exception: defer after error check
f, err := os.Open("file.txt")
if err != nil {
return err
}
defer f.Close()
```

</td></tr>

<tr><td valign="top">

<sup>1</sup> Missing whitespace after block

<sup>2</sup> Missing whitespace after block

</td><td valign="top">

</td></tr>
</tbody></table>

### `trailing-whitespace`

<table>
Expand Down Expand Up @@ -1357,3 +1421,20 @@ if anotherVariable {
}
}
```

### `case-max-lines`

When set to a value greater than 0, case clauses in `switch` and `select`
statements that exceed this number of lines will require a blank line before the
next case. Setting this to 1 will make it always enabled.

When a single comment group exists between case clauses, the blank line
placement depends on the comment's start indentation:

- Comments indented like statements (deeper than `case`) are treated as trailing
comments. The blank line goes after them.
- Comments indented at the same level as `case` are treated as leading comments.
The blank line goes before them.

If there are multiple comment groups between cases, the check is skipped to
avoid ambiguous situations.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ For more details and examples, see [CHECKS](CHECKS.md).
- ✅ **err** - Error checking must follow immediately after the error variable
is assigned
- ✅ **leading-whitespace** - Disallow leading empty lines in blocks
- ❌ **newline-after-block** - Require empty line after block statements
- ✅ **trailing-whitespace** - Disallow trailing empty lines in blocks

### Configuration
Expand Down Expand Up @@ -167,6 +168,7 @@ linters:
disable:
- assign-exclusive
- assign-expr
- newline-after-block
```

## See also
Expand Down
17 changes: 17 additions & 0 deletions analyzer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,23 @@ func TestWithConfig(t *testing.T) {
config.Checks.Add(CheckAppend)
},
},
{
subdir: "all_enabled",
configFn: func(config *Configuration) {
config.Checks = AllChecks()
config.CaseMaxLines = 1
},
},
{
subdir: "newline_after_block",
configFn: func(config *Configuration) {
config.Checks = NoChecks()
config.Checks.Add(CheckCaseTrailingNewline)
config.Checks.Add(CheckNewlineAfterBlock)

config.CaseMaxLines = 1
},
},
} {
t.Run(tc.subdir, func(t *testing.T) {
t.Parallel()
Expand Down
5 changes: 5 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ const (
CheckErr
CheckLeadingWhitespace
CheckTrailingWhitespace
CheckNewlineAfterBlock

//nolint:godoclint // No need to document
// CheckTypes only used for reporting.
Expand Down Expand Up @@ -102,6 +103,7 @@ func (c CheckType) String() string {
"err",
"leading-whitespace",
"trailing-whitespace",
"newline-after-block",
//
"case-trailing-newline",
}[c]
Expand Down Expand Up @@ -211,6 +213,7 @@ func AllChecks() CheckSet {
c := DefaultChecks()
c.Add(CheckAssignExclusive)
c.Add(CheckAssignExpr)
c.Add(CheckNewlineAfterBlock)

return c
}
Expand Down Expand Up @@ -274,6 +277,8 @@ func CheckFromString(s string) (CheckType, error) {
return CheckLeadingWhitespace, nil
case "trailing-whitespace":
return CheckTrailingWhitespace, nil
case "newline-after-block":
return CheckNewlineAfterBlock, nil
default:
return CheckInvalid, fmt.Errorf("invalid check '%s'", s)
}
Expand Down
2 changes: 1 addition & 1 deletion config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ func TestCheckSet(t *testing.T) {
func TestToAndFromString(t *testing.T) {
t.Parallel()

maxCheckNumber := 23
maxCheckNumber := 24

for n := range maxCheckNumber {
check := CheckType(n)
Expand Down
20 changes: 20 additions & 0 deletions testdata/src/default_config/whitespace/leading_whitespace.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,26 @@ func commentThenLeadingWhitespace() { // want +2 `unnecessary whitespace \(leadi
fmt.Println("Hello, World")
}

// want +3 `unnecessary whitespace \(leading-whitespace\)`
// want +4 `unnecessary whitespace \(leading-whitespace\)`
func whitespaceBeforeAndAfterComment() {

// Whitespace before and after comment

fmt.Println("Hello, World")
}

// want +3 `unnecessary whitespace \(leading-whitespace\)`
// want +6 `unnecessary whitespace \(leading-whitespace\)`
func whitespaceBeforeAndAfterMultpleComment() {

// Whitespace before and after comment

// Some more comments

fmt.Println("Hello, World")
}

func noWhitespace() {
fmt.Println("Hello, World")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,22 @@ func commentThenLeadingWhitespace() { // want +2 `unnecessary whitespace \(leadi
fmt.Println("Hello, World")
}

// want +3 `unnecessary whitespace \(leading-whitespace\)`
// want +4 `unnecessary whitespace \(leading-whitespace\)`
func whitespaceBeforeAndAfterComment() {
// Whitespace before and after comment
fmt.Println("Hello, World")
}

// want +3 `unnecessary whitespace \(leading-whitespace\)`
// want +6 `unnecessary whitespace \(leading-whitespace\)`
func whitespaceBeforeAndAfterMultpleComment() {
// Whitespace before and after comment

// Some more comments
fmt.Println("Hello, World")
}

func noWhitespace() {
fmt.Println("Hello, World")
}
Expand Down
96 changes: 96 additions & 0 deletions testdata/src/with_config/all_enabled/all_enabled.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package testpkg

import "fmt"

func e() (bool, error) {
return false, nil
}

func blockFollowedByIf() {
x, y := 1, 2
if x > 0 {
_ = 1
} // want `missing whitespace below this line \(newline-after-block\)`
if y > 0 { // want `missing whitespace above this line \(invalid statement above if\)`
_ = 1
}
}

func blockFollowedByFor() {
x, y := 1, 2
if x > 0 {
_ = 1
} // want `missing whitespace below this line \(newline-after-block\)`
for i := 0; i < y; i++ { // want `missing whitespace above this line \(invalid statement above for\)`
_ = i
}
}

func blockFollowedBySwitch() {
x, y := 1, 2
if x > 0 {
_ = 1
} // want `missing whitespace below this line \(newline-after-block\)`
switch y { // want `missing whitespace above this line \(invalid statement above switch\)`
case 1:
_ = 1
}
}

func consecutiveBlocks() {
if true {
_ = 1
} // want `missing whitespace below this line \(newline-after-block\)`
if false { // want `missing whitespace above this line \(invalid statement above if\)`
_ = 1
} // want `missing whitespace below this line \(newline-after-block\)`
for i := 0; i < 1; i++ { // want `missing whitespace above this line \(invalid statement above for\)`
_ = i
} // want `missing whitespace below this line \(newline-after-block\)`
switch 1 { // want `missing whitespace above this line \(invalid statement above switch\)`
case 1:
_ = 1
}
}

func caseAndBlockNewlines(n int) {
switch n {
case 1:
_ = 1 // want `missing whitespace below this line \(case-trailing-newline\)`
case 2:
_ = 1
} // want `missing whitespace below this line \(newline-after-block\)`
fmt.Println("") // want `missing whitespace above this line \(invalid statement above expr\)`
}

func errCheckAndNewlineAfterBlock() {
b, err := e() // want +1 `unnecessary whitespace \(err\)`

if err != nil {
fmt.Println("")
} // want `missing whitespace below this line \(newline-after-block\)`
fmt.Println(b) // want `missing whitespace above this line \(invalid statement above expr\)`
}

func multipleOnSameStatement() {
a := 1
b := 2 // want `missing whitespace above this line \(too many statements above if\)`
if a > 0 {
_ = b
} // want `missing whitespace below this line \(newline-after-block\)`
c := 3 // want `missing whitespace above this line \(invalid statement above assign\)`

_ = c
}

func nestedBlocksAllNeedNewlines() {
if true {
if true {
_ = 1
} // want `missing whitespace below this line \(newline-after-block\)`
if false { // want `missing whitespace above this line \(invalid statement above if\)`
_ = 1
} // want `missing whitespace below this line \(newline-after-block\)`
} // want `missing whitespace below this line \(newline-after-block\)`
_ = 1 // want `missing whitespace above this line \(invalid statement above assign\)`
}
Loading
Loading