3

I'm configuring SwiftLint for a project, and one of our standards that I would like to enforce is this: when a function is declared or called, and its parameters or arguments are broken over multiple lines, then the first parameter should always be on the line after the function name.

In other words, it should always look like this:

func foo(
  bar: Int,
  baz: Int
  ...

foo(
  bar: 0,
  baz: 1

and never like this:

func foo(bar: Int
         baz: Int
         ...

foo(bar: 0,
    baz: 1
    ...

I've looked for a rule like this in among the predefined rules, but I couldn't find one. I'm really hoping I just missed it, because this seems like a rule that could be auto-applied with --fix quite easily.

If no such rule exists, I suppose it wouldn't be too hard to create a custom rule, but then (to my understanding) setting it up to be auto-applied is out of the question. Or am I wrong?

Phlippie Bosman
  • 5,378
  • 3
  • 26
  • 29

2 Answers2

3

To answer my own question:

  1. No, this rule doesn't seem to be supported out of the box. The closest that seems to exist is multiline_parameters, which is perfectly happy with the snippet that I would want to consider a violation
  2. No, SwiftLint doesn't support autocorrecting custom rules
  3. My custom rule still needs a lot of love, but here is what I've got so far, which at least doesn't seem to trigger false positives:
# Triggered when a multi-line parameter or argument list starts on the same line as the opening bracket
# func foo(x: Int,
#      y: Int...
# ---OR---
# foo(x: 1,
#     y: 2...
    multi_line_args_start_on_same_line:
        name: "Multi-line args format"
        message: "Multi-line arguments or parameters should start on a new line"
        included: ".*\\.swift"
        # Line-by-line:
        # - start of function with opening bracket; e.g. `foo(`
        # - A parameter name, then a colon, and then whitespace; e.g. `x: `
        # - A parameter value or type, followed by a comma and newline,
        #   e.g. `Int,\n` or `10,\n`
        # - Anything, to account for subsequent parameters or args
        # - A closing bracket at the end
        regex: "\
        [\\w\\d]+\\(\
        [\\w\\d]+:\\s*\
        [\\w\\d]+,\\n\
        .*\
        \\)$"
        severity: error 
Phlippie Bosman
  • 5,378
  • 3
  • 26
  • 29
1

I believe that if you use first_argument_location = next_line as a configuration to multiline_parameters rule, then you can get the desired behaviour. As we can see on the test found on SwiftLint source code, however that is not well explained on docs.

@testable import SwiftLintFramework

class MultilineArgumentsRuleTests: SwiftLintTestCase {
    func testMultilineArgumentsWithWithNextLine() {
        let nonTriggeringExamples = [
            Example("foo()"),
            Example("foo(0)"),
            Example("foo(1, bar: baz) { }"),
            Example("foo(2, bar: baz) {\n}"),
            Example("foo(\n" +
            "    3,\n" +
            "    bar: baz) { }"),
            Example("foo(\n" +
            "    4, bar: baz) { }")
        ]

        let triggeringExamples = [
            Example("foo(↓1,\n" +
            "    bar: baz) { }")
        ]

        let description = MultilineArgumentsRule.description
            .with(triggeringExamples: triggeringExamples)
            .with(nonTriggeringExamples: nonTriggeringExamples)

        verifyRule(description, ruleConfiguration: ["first_argument_location": "next_line"])
    }

https://github.com/realm/SwiftLint/blob/main/Tests/SwiftLintFrameworkTests/MultilineArgumentsRuleTests.swift