esimonov / ifshort

Go linter for checking that your code uses short syntax for if-statements whenever possible.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Confusing suggestion when multiple variables are only-used in if statement

invidian opened this issue · comments

Consider following diff:

diff --git internal/util/util_test.go internal/util/util_test.go
index 3e9eb1e..b47a3da 100644
--- internal/util/util_test.go
+++ internal/util/util_test.go
@@ -64,8 +64,7 @@ func TestPickIntFirst(t *testing.T) {
 func TestIndent(t *testing.T) {
        t.Parallel()

-       expected := "   foo"
-       if a := Indent("foo", "   "); a != expected {
+       if a, expected := Indent("foo", "   "), "   foo"; a != expected {
                t.Fatalf("expected '%s', got '%s'", expected, a)
        }
 }

While it is true that expected is only used in if block, I think moving it's declaration into if statement makes it very confusing, harder to read and generally uncommon.

Is this behavior intentional? I have doubts that this suggestion should be promoted.

@invidian Thank you for your interest!

In short - yes, at the moment this behaviour is intentional.

If more verbosely, I agree that in tests that's a very common practice to compare values with something expected. Hovewer, outside tests it's usually desired to keep the context for a variable as succinct as possible (at least, ifshort was built with this idea in mind).
I suspect this linter is one of those which are better off excluded from checking tests files, but I didn't want to impose this behaviour out-of-the-box. You can disable it for tests in your golangci.yaml config:

issues:
  exclude-rules:
    - path: _test\.go
      linters:
        - ifshort

(please have a look at the official docs for more information)

By the way, how would you formalise your intuition for this suggestion being unwanted? Does it have to do with right-hand side of the assignment being a literal of basic type?

Thanks for the reply @esimonov! I'm aware of option to exclude certain rules from golangci-lint, but thank you for suggestion. I would still like to lint test files though, for example to still catch all err := and _, err := cases etc.

By the way, how would you formalise your intuition for this suggestion being unwanted? Does it have to do with right-hand side of the assignment being a literal of basic type?

I'm not sure if I understand your question. I think this suggestion is not great because of how Go allows you to assign values inside if statement. Writing the following code:

if a, expected := Indent("foo", "   "), "   foo"; a != expected {

makes you "jump" to figure out which value of N assigned goes into which variable.

I guess the following code could be more readable:

if a := Indent("foo", "   "); expected := "   foo"; a != expected {

However, it is not a valid Go syntax.

Perhaps some assert helper would be useful here as well, so one could just do:

not(equal(t, Indent("foo", "   "), "   foo"))

I wonder if this behavior could be somehow configurable. I saw max-decl-lines configuration option, but I think it does different thing.

@invidian Aha, now I see what you mean. So the parameter you care about is a 'number of assignments that needs to be performed inside an if-statement'.

max-decl-linesand (especially) max-decl-chars address a similar yet a bit different problem - assignment length, in order to avoid long if-statements (literally long; i.e containing more symbols than allowed by the configuration).

Well, your enhancement request makes sense to me, however it doesn't look like something that I can implement in one evening.
I'll ponder on possible ways of introducing this feature, and won't close your issue until I'm certain about my plans.

max-decl-linesand (especially) max-decl-chars address a similar yet a bit different problem - assignment length, in order to avoid long if-statements (literally long; i.e containing more symbols than allowed by the configuration).

I'm not sure how specifically, but I think lll linter also covers long if statements.

Well, your enhancement request makes sense to me, however it doesn't look like something that I can implement in one evening.
I'll ponder on possible ways of introducing this feature, and won't close your issue until I'm certain about my plans.

Thanks for understanding and open-mindedness :) I have no experience with implementing linters, so I'm not sure if I'll be able to help much here.

If you come up with something, please let me know, I'm happy to run it across my code bases to find potential issues.

lll restricts any line of your code from being too long.
max-decl-chars and max-decl-lines don't complain about line length - instead, they tell ifshort to calm down when assignment is aleady too long / too 'high', and moving it inside the if-statement would make it look even uglier.

For example, consider this snippet.

func notUsed_LongDecl_OK() {
	v := getValue("Long long long long long declaration, linter shouldn't force short syntax for it.")
	if v != nil {
		noOp1(v)
	}
}

Linter won't suggest any changes as the assignment to v is more than 30 (default value of max-decl-chars) characters long.

A similar logic applies to this snippet

func notUsed_HighDecl_OK() {
	v := getValue(
		nil,
		nil,
		nil,
	)
	if v != nil {
		noOp1(v)
	}
}

The assignment to v is 5 lines 'high' , more than the default value of 1 for max-decl-lines, so ifshort will stay silent.

I probably need to document that somewhere in README :)