-
Notifications
You must be signed in to change notification settings - Fork 5
Description
Hi!
I'd like to propose extending the whitespace linter with complementary functionality that enforces blank lines after block statements as a new "opt-in" feature. This would pair naturally with whitespace's existing checks that remove unnecessary whitespace inside blocks, creating a comprehensive block-level whitespace management tool.
An implementation (based on go/analysis, including tests) of the proposed linter functionality can be found in newline-after-block.
Background
I am a contributor to multiple projects in the Incus ecosystem like incus, operations-center and migration-manager. All of these projects have the use of a simple linting shell script in common, which is called newline-after-block.sh. This script ensures a newline after each functional block (after if, for, switch/select, etc.). This is done by some sed and grep magic, which has some critical limitations:
- It does not distinguish between block statements and composite literals, it would incorrectly flag struct/map/slice literals because the detection logic relies on the ending curly brace
}. - It does not handle nested blocks correctly due to the way empty lines are detected after a closing curly brace, it would fail to detect a missing empty line after two closing curly braces (e.g. nested if)
- it does not provide automatic fix capability, all corrections need to be done manually.
I created a simple newline-after-block linter in Go using the go/analysis framework, which does the job and resolves the above mentioned shortcomings of the shell script.
Why include in whitespace
Since our projects are also linted using golangci-lint, we would like to get this functionality included, which leaves us with two options:
- Ask for inclusion of newline-after-block into
golangci-lintdirectly. - Extend an existing linter with the required functionality.
In regards to option 1, I have already gone this route in the past (bidichk, errchkjson) and from this experience I know, that the maintainers of golangci-lint prefer to reduce duplication by multiple linters. Therefore I checked the existing linters already included in golangci-lint for similar functionality, which left me with two candidates:
whitespace(this linter)wsl
After a quick analysis of both, I figured, that whitespace is the more natural fit for the following reasons:
- Both linters manage whitespaces
- Both linters share the same technical foundation, in particular
go/analysisframework andanalysistest - Both implement SuggestedFixes with TextEdit for automatic corrections
- Both use analysistest for testing
- Both operate at the package level
In contrast, Inclusion into wsl does not build on the go/analysis framework and uses a lot of custom logic instead.wsl would require a lot more work and some goals of newline-after-block might even be conflicting with the rules currently present in wsl.
The proposed checks are the natural counterpart to whitespace's existing functionality without overlap or conflict.
Proposed Checks
Newline After Block Statements
Enforce blank lines after these block statements (when not at the end of a statement list):
- if statements (only when not followed by else)
- for loops
- range loops
- switch statements
- type switch statements
- select statements
Newline Between Case Clauses
Enforce blank lines between non-empty case blocks within switch, type switch, and select statements (except before the closing brace).
Examples of What Would Be Detected
Example 1: Missing newline after if block
// Would be flagged
func process(condition bool) {
if condition {
doSomething()
} // want "missing newline after block statement"
nextStatement()
}
// After auto-fix
func process(condition bool) {
if condition {
doSomething()
}
nextStatement()
}Example 2: Missing newline after for loop
// Would be flagged
func iterate() {
for i := 0; i < 10; i++ {
process(i)
} // want "missing newline after block statement"
fmt.Println("done")
}
// After auto-fix
func iterate() {
for i := 0; i < 10; i++ {
process(i)
}
fmt.Println("done")
}Example 3: Missing newline after switch statement
// Would be flagged
func handleValue(v int) {
switch v {
case 1:
handleOne()
case 2:
handleTwo()
} // want "missing newline after block statement"
cleanup()
}
// After auto-fix
func handleValue(v int) {
switch v {
case 1:
handleOne()
case 2:
handleTwo()
}
cleanup()
}Example 4: Missing newlines between case clauses
// Would be flagged
func handleSwitch(v int) {
switch v {
case 1:
handleOne() // want "missing newline after case block"
case 2:
handleTwo() // want "missing newline after case block"
default:
handleDefault()
}
}
// After auto-fix
func handleSwitch(v int) {
switch v {
case 1:
handleOne()
case 2:
handleTwo()
default:
handleDefault()
}
}Example 5: Correctly ignored cases
// Block at end of function
func endOfFunc() {
if condition {
doSomething()
}
}
// if-else chain
func withElse() {
if condition {
doSomething()
} else {
doSomethingElse()
}
}
// Composite literal (not a block statement)
func structInit() {
p := Person{
Name: "Alice",
Age: 30,
}
fmt.Println(p)
}
// Empty case (no newline needed)
func emptyCase(v int) {
switch v {
case 1:
case 2:
handleTwo()
}
}For more examples, check out the test cases.
Suggested Configuration Options
Following whitespace's pattern with -multi-if and -multi-func, I suggest adding:
-newline-after-block, Enforce newline after blocks (if, for, switch/select) (default:falsefor backwards compatibility)-newline-between-cases, Enforce newlines between case clauses in switch/select (default:falsefor backwards compatibility)
Potential Future Extensions
- Rules about newlines around
defer(I prefer to have a newline after adeferstatement. In most cases, I don't want to have a newline beforedefer, but this is not a strict rule. But it should be allowed to omit the newline after a block ifdeferis used right after the block (e.g. in the case, open file, check error, defer close, where these 3 parts belong together).
Final thoughs
I hope, this proposal finds your interest and we can work together on the inclusion of this functionality in the whitespace linter.
Edits:
wsldoes also usego/analysis