Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Is it possible to enforce parameters are on their own line? #620

Closed
AdamMc331 opened this issue Oct 16, 2019 · 22 comments
Closed

Is it possible to enforce parameters are on their own line? #620

AdamMc331 opened this issue Oct 16, 2019 · 22 comments
Labels

Comments

@AdamMc331
Copy link
Contributor

I found myself in a scenario where I had multiple parameter on one line, with others on a different line, like this:

fun test(one: String, two: String, three: String, four: String) {
}

fun callTest() {
    test(
        "One",
        "Two", "Three", "Four"
    )
}

KtLint actually passes this, because there is a new line after the opening ( and one before the closing ), but I still think it's weird to have one parameter on one line and three on the other.

I'd really prefer it be an all or nothing thing:

// This
test("One", "Two", "Three", "Four")
// Or
test(
    "One",
    "Two",
    "Three",
    "Four"
)

Any thoughts on a way to enforce this, if it's possible?

@Tapchicoma
Copy link
Collaborator

Should be linted by "parameter-list-wrapping" rule.

Could you add a test case for your problem and check if it covered by this rule?

@romtsn
Copy link
Collaborator

romtsn commented Nov 12, 2019

I looked this up a little bit, and looks like this is actually a never-implemented argument-list-wrapping. The PR has been closed in favour of IndentationRule - so I guess this use-case should go there. Or we might need to think of introducing that rule nonetheless (or perhaps extending parameter-list-wrapping to support arguments).

@AdamMc331
Copy link
Contributor Author

Sorry I haven't had much time to come back to this issue. I'll dig in this week and try to add a test case like Tapchicoma suggested and report back on that!

@AdamMc331
Copy link
Contributor Author

Yeah, it looks like this would be called for function parameters but not for call arguments:

// THIS TEST PASSES
@Test
fun testLintFunctionParameterInconsistency() {
    assertThat(
        ParameterListWrappingRule().lint(
            """
                fun f(
                    a: Any,
                    b: Any, c: Any
                )
            """.trimIndent()
        )
    ).isEqualTo(
        listOf(
            LintError(3, 13, "parameter-list-wrapping", "Parameter should be on a separate line (unless all parameters can fit a single line)")
        )
    )
}

// THIS TEST FAILS
@Test
fun testLintArgumentInconsistency() {
    assertThat(
        ParameterListWrappingRule().lint(
            """
                val x = f(
                    a: Any,
                    b: Any, c: Any
                )
            """.trimIndent()
        )
    ).isEqualTo(
        listOf(
            LintError(3, 13, "parameter-list-wrapping", "Parameter should be on a separate line (unless all parameters can fit a single line)")
        )
    )
}

I will dig into how it's linted on the function parameters & see if I can reuse that logic at the call sites, too. Let me know what you think about extending parameter-list-wrapping vs a new rule.

@JoseAlcerreca
Copy link

Hi! Any updates on this?

@AdamMc331
Copy link
Contributor Author

Wow I forgot to follow up on this! Assuming it hasn't been fixed under a different ticket, I can revisit this weekend.

@romtsn
Copy link
Collaborator

romtsn commented Jun 11, 2020

I'd say extending parameter-list-wrapping is fine in this case, as they are very similar to each other, and I can't imagine using one w/o another. But in case there's not much in common for implementation of these rules, probably a new one can be created

@AdamMc331
Copy link
Contributor Author

Very close to a working solution: https://github.com/pinterest/ktlint/compare/master...AdamMc331:KTL-620/argument_wrapping?expand=1

However, I broke another test inside ParameterListWrappingRuleTest:

@Test
    fun testFormatPreservesIndentWithAnnotations() {
        assertThat(
            ParameterListWrappingRule().format(
                """
                class A {
                    fun f(@Annotation
                          a: Any,
                          @Annotation([
                              "v1",
                              "v2"
                          ])
                          b: Any,
                          c: Any =
                              false,
                          @Annotation d: Any) {
                    }
                }
                """.trimIndent()
            )
        ).isEqualTo(
            """
            class A {
                fun f(
                    @Annotation
                    a: Any,
                    @Annotation([
                        "v1",
                        "v2"
                    ])
                    b: Any,
                    c: Any =
                        false,
                    @Annotation d: Any
                ) {
                }
            }
            """.trimIndent()
        )
    }

With my change, this tries to format the arguments inside the annotation, and I have a feeling we don't actually want that based on the name of the test. So, I wonder if there's some way to see if it's VALUE_ARGUMENT inside some annotation? 🤷‍♂️

@romtsn
Copy link
Collaborator

romtsn commented Jun 14, 2020

@AdamMc331 yeah, it is VALUE_ARGUMENT for this annotation:

  @Annotation([
      "v1",
      "v2"
  ])

Have you seen the PsiViewer IntelliJ plugin? Works best for me in those cases

@AdamMc331
Copy link
Contributor Author

I'll check out the plugin!

So, with regards to that annotation, it appears my change now wants to reformat that and put the square bracket on its own line. And I'm guessing that's an unintended consequence and I'm not sure what to do next. This doesn't feel right to me. 🤔

Screen Shot 2020-06-14 at 6 27 57 PM

@romtsn
Copy link
Collaborator

romtsn commented Jun 15, 2020

I've just checked it - idea itself reformats it this way for me with official styleguide applied:

@Annotation(
    [
        "v1",
        "v2"
    ]
)

So I'd say it's fine to introduce this change.

@AdamMc331
Copy link
Contributor Author

Sounds good, will update test & get a PR up soon.

@AdamMc331
Copy link
Contributor Author

I don't get the exact same format in the unit test, I get something like this:

class A {
    fun f(
        @Annotation
        a: Any,
        @Annotation(
            [
            "v1",
            "v2"
        ]
        )
        b: Any,
        c: Any =
            false,
        @Annotation d: Any
    ) {
    }
}

That's why I was worried to add this.

@AdamMc331
Copy link
Contributor Author

It wants to put the brackets in a weird space and idk why. You mentioned "with official styleguide applied" is it possible that's not the default in a unit test?

@romtsn
Copy link
Collaborator

romtsn commented Jun 15, 2020

What I meant is when you put this code into idea and hit the autoformat hotkey it will format to what I mentioned above.

But this seems like we need to maintain indentation here as well.. Just curious, if you try to add something more sophisticated as a test case, like this:

    test(
        one("a", "b",
        "c"),
        "Two", "Three", "Four"
    )

How would it reformat?

@AdamMc331
Copy link
Contributor Author

So that one is able to print what I'd expect:

val x = test(
    one(
        "a",
        "b",
        "c"
    ),
    "Two",
    "Three",
    "Four"
)

I've added the test case: 1c40419

Let me look into the linting rule a little more for the indentation and see what I can find. I'm guessing that square brackets and parens are different PSI elements so they're treated differently. Might have to dig into the plugin you mentioned. 🕵️

@romtsn
Copy link
Collaborator

romtsn commented Jun 15, 2020

yeah, that's good, maybe the rule just needs additional handling for the square brackets

@AdamMc331
Copy link
Contributor Author

Yeah it looks like it's treating ["v1", "v2"] as a single VALUE_ARGUMENT, so it's getting put on a new line and indented, which is why we see the first square bracket have a weird indent.

So if I can tweak the rule to look for an argument that starts and ends with a COLLECTION_LITERAL_EXPRESSION I may be able to sort this out.

@AdamMc331
Copy link
Contributor Author

I got closer to a working solution by considering elements inside a collection literal: 95bf698

Unfortunately it's not perfect, as something about the whitespace before the right bracket is screwy. But we're getting closer haha.

@romtsn romtsn closed this as completed in a94b43f Aug 5, 2020
@alexm-lasso
Copy link

Why was this changed? The official coding style allows multiple parameters on the same line for method calls:
https://kotlinlang.org/docs/reference/coding-conventions.html#method-call-formatting

Group multiple closely related arguments on the same line.

@romtsn
Copy link
Collaborator

romtsn commented Sep 2, 2020

@alexm-lasso yeah, we realized that we should've put this into the experimental ruleset first to make it opt-in rather than opt-out and should've not merged it with parameter-list-wrapping.. we'll try to make this happen for the next release

@withoutclass
Copy link

withoutclass commented Oct 21, 2020

I've found that this change is causing an issue where enforcing the parameter list wrapping can then introduce indentation errors, leading to an issue that is not fixable by running ktlint itself. The issue I'm experiencing is that when an if block contains multiple arguments and one of those arguments is a function that takes multiple parameters, the result of ktlintFormat introduces an indentation error.

For example
original:

.subscribe({
    if (foo.isBlank() && !Util.isValid(foo.address)) {
        bar.doThing()
    } else {
        bar.clear()
    }
})

result:

.subscribe(
    {
        if (foo.isBlank() && !Util.isValid(
                foo.address
            )
        ) {
            bar.doThing()
        } else {
            bar.clear()
        }
    }
)

manual intervention that solves the introduced indentation problem:

.subscribe(
    {
        if (
            foo.isBlank() && 
            !Util.isValid(foo.address)
        ) {
            bar.doThing()
        } else {
            bar.clear()
        }
    }
)

Interestingly the Intellij formatter would break the line at .isBlank() which seems...insane to me, as opposed to naturally wrapping on the &&.

edit: Aha, I found #764 which I think would fix this problem.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

6 participants