Golang style linters, multiline statements, and blocks with whitespace

The golang ci lint linters whitespace, lll, and wsl can put you in a cycle of warnings about your code quality. By adding a comment to the right place you can appease all three.

Golang style linters, multiline statements, and blocks with whitespace

Whew, this is a long title. No idea whether it's going to show up on the search engines. Anyhow...

So as I’m working through Advent of Code 2021 (yes, still), I keep getting super annoying linter messages. I use a fairly comprehensive golangci-lint ruleset, but the three that are relevant here are these:

linters:
  disable-all: true
  enable:
    - lll
    - whitespace
    - wrapcheck

There are others, plus the configurations, but that’s not important right now.

So these three have a very weird interaction where you can get into a cycle of “wait, how do I even solve this?” Take this piece of Go code for example:

func something(in someStruct) {
	if in.xFrom > cubeUpperLimit || in.yFrom > cubeUpperLimit || in.zFrom > cubeUpperLimit || in.xTo < cubeLowerLimit || in.yTo < cubeLowerLimit || in.zTo < cubeLowerLimit {
		return instruction{}, errors.New("out of bounds")
	}

	// the rest of the function
}

That’s a pretty long if condition. The lll linter will complain that the line is too long:

line is 170 characters (lll)
	if in.xFrom > cubeUpperLimit || in.yFrom > cubeUpperLimit || in.zFrom > cubeUpperLimit || in.xTo < cubeLowerLimit || in.yTo < cubeLowerLimit || in.zTo < cubeLowerLimit {

Okay, let’s break it up:

func something(in someStruct) {
	if in.xFrom > cubeUpperLimit ||
		in.yFrom > cubeUpperLimit ||
		in.zFrom > cubeUpperLimit ||
		in.xTo < cubeLowerLimit ||
		in.yTo < cubeLowerLimit ||
		in.zTo < cubeLowerLimit {
		return instruction{}, errors.New("out of bounds")
	}

	// rest of the function
}

Looks better. Except now I'm getting this error:

multi-line statement should be followed by a newline (whitespace)
		in.zTo < cubeLowerLimit {

No problem, I can just add a newline after the opening {

func something(in someStruct) {
	if in.xFrom > cubeUpperLimit ||
		in.yFrom > cubeUpperLimit ||
		in.zFrom > cubeUpperLimit ||
		in.xTo < cubeLowerLimit ||
		in.yTo < cubeLowerLimit ||
		in.zTo < cubeLowerLimit {

		return instruction{}, errors.New("out of bounds")
	}

	// rest of the function
}

Hah, no dice, I’m now getting this:

block should not start with a whitespace (wsl)
		in.zTo < cubeLowerLimit {
		                        ^

And therein lies the issue... seems like these three linters can’t be satisfied at the same time. Scouring the internet I found two vaguely helpful links: https://github.com/ultraware/whitespace/issues/1 and https://github.com/golang/lint/issues/459. Both are above whitespace after multiline if statements, but neither tackle the contradiction between these two linting rules together.

Solution

The only way I could make everything happy is if I add a code comment to the line immediately after the if block, like so:

func something(in someStruct) {
	if in.xFrom > cubeUpperLimit ||
		in.yFrom > cubeUpperLimit ||
		in.zFrom > cubeUpperLimit ||
		in.xTo < cubeLowerLimit ||
		in.yTo < cubeLowerLimit ||
		in.zTo < cubeLowerLimit {
		// If any part of the cube is outside our bounds, then return an error.
		return instruction{}, errors.New("out of bounds")
	}

	// rest of the function
}

This way the lines aren’t too long, there’s a newline after the multi-line statement, and the block does not start with whitespace. Win–win–win!

Photo by JESHOOTS.COM on Unsplash