How to add leading trivia recursively to a CodeBlockItemListSyntax?

I have this sample piece of code:

func something(_ a: Int) -> String {
    if a == 10 {
        return "hello"
    }
    return "bye"
}

I want to add a tab Trivia to every line inside a function.
Here is what I've tried:

extension SyntaxProtocol {
    func appendingTabToLeadingTrivia() -> Self {
        var trivia = leadingTrivia.appending(.tab)
        var copy = self
        copy.leadingTrivia = trivia
        return copy
    }
}

extension CodeBlockItemListSyntax {
  func appendingTabToLeadingTriviaForAllChildren() -> Self {
      var newCodeBlockItemSyntaxes: [Element] = []
   
      // Iterate throught every CodeBlockItemList and add a `tab` Trivia
      self.forEach { block in
          var copy = block
          copy = copy.appendingTabToLeadingTrivia()
          newCodeBlockItemSyntaxes.append(copy)
      }
  
      return .init(newCodeBlockItemSyntaxes)
  }
}

func modify(originalSyntax: CodeBlockItemListSyntax) {
    var copy = originalSyntax.appendingTabToLeadingTriviaForAllChildren()
    debugPrint(copy.description)
}

Expected

func something(_ a: Int) -> String {
        if a == 10 {
            return "hello"
        }
        return "bye"
}

Actual Result

func something(_ a: Int) -> String {
        if a == 10 {
        return "hello"
    }
        return "bye"
}

Questions:

Seems like my approach only adds a tab to the first line of a CodeBlockItemList.
How can I achieve the expected result?

2 Likes

Hi @ahoppen,
I hope you can give me some advice :pray:

This is a very good question!

The result you see is expected. An item in a code block is not a single line but comprises complete syntax nodes. In your example, the if expression is one node and the return statement is another. By prepending some whitespace to every item, only the first token in every item experiences the indentation, that is the if keyword and the return keyword.

Now, to achieve an indentation of a whole block, you'd need to iterate over all tokens and prepend the whitespace after every (last) newline in the token's trivia.

SwiftSyntax itself doesn't seem to have such an algorithm built-in. However, as this is something important for SwiftLint, I have implemented a rewriter that would do what is explained above. It allows indentation and unindentation (Is this a term anyway? :thinking:) with tabs and spaces. It works for the single SwiftLint rule that currently uses it. So there's no guarantee I thought of all the details required to make this a general component.

1 Like

Thank you @SimplyDanny :pray: It works for me :grin: