libSyntax API docs and usage

I have been trying to use libSyntax for some basic refactoring and have a couple of questions:

a) Is there some documentation available for the various APIs in SyntaxFactory and the visitor functions? The libSyntax github link has a useful example but the locations of the docs was not very obvious.

b) I am trying to create an object of type StmtSyntax from the node.body of IfStmt.
But, the bridge between the node.body's CodeBlockItemListSyntax (given by node.body.statements) and the StmtSyntax object is unclear. The code below uses makeIfStmt and achieves the purpose partially:

   override func visit(_ node: IfStmtSyntax) -> StmtSyntax {
      if some_condition {
         return SyntaxFactory.makeIfStmt(labelName: node.labelName, labelColon: node.labelColon, ifKeyword: SyntaxFactory.makeUnknown(""), conditions: SyntaxFactory.makeBlankConditionElementList(), body: node.body, elseKeyword: SyntaxFactory.makeUnknown(""), elseBody: SyntaxFactory.makeBlankElseBlock())
       } else  {
         return super.visit(node)
       }
    }

It rewrites

if cond { 
    //then 
 }

into

{ 
   //then
} 

My goal is to just return a

//then. 

Do you have pointers on how to do this?

c) Also, if there are tutorials on usage of libSyntax, please do share.

Thanks!

Hey @swift-analyzer,

We don't have extensive documentation for syntax factory yet. Most of our existing doc-comments are for SyntaxNodes. If you have specific questions about syntax factory or visitor functions, feel free to ask here in the forum.

First of all, the //then part of the code is attached to the closing brace } since there're no other statement after //then. To extract the dangling comments like this, we have to visit the end brace } instead of collecting node.body. One principle of libSyntax's structure is that comments must attach to an existing token syntax.

To walk-around the bridging problem, the following code example works for me:

  override func visit(_ node: IfStmtSyntax) -> StmtSyntax {
    return SyntaxFactory.makeIfStmt(labelName: node.labelName, labelColon: node.labelColon, 
      ifKeyword: SyntaxFactory.makeUnknown(""), conditions: SyntaxFactory.makeBlankConditionElementList(), 
      body: node.body.withRightBrace(nil).withLeftBrace(nil), elseKeyword: SyntaxFactory.makeUnknown(""),
      elseBody: SyntaxFactory.makeBlankElseBlock())
  }

Notice we need to erase the braces from node.body.

1 Like

Thanks @Xi_Ge. It works by removing the braces but does not remove the blank spaces (the trivia?) attached to the node, thereby leaving the existing indentation, due to the if condition, intact. I want the indentation of the entire then body to be removed as well. I couldn't find APIs (similiar to withoutLeadingTrivia...) on CodeBlockSyntax (node.body) that removes the trivia. Do you have suggestions for fixing that?

PS: I can potentially run swiftformat on the modified code (which does the job, btw!) but I want to understand these APIs in more detail.

I'd say if you want to convert

if cond {
  // then
}

to

// then

Then the think you want to visit is CodeBlockItemListSyntax, and iterate over all elements in the code block. You'll need to do this because you can't replace a single if statement with potentially multiple statements inside the if statement -- you have to add the inner statements to the code block which the if statement lives inside.

For example, consider this

- <code block>
  - print("foo")
  - if cond
    - <code block>
      - print("hello")
      - print("goodbye")
  - print("bar")

The body of the if statement contains multiple children, so you'll need to actually add both of those to the outer code block.

We had to do this in our swift-format project for converting:

for x in y {
  if cond {
    // then
  }
}

to

for x in y where cond {
  // then
}

(The code for this transformation is here: https://github.com/google/swift/blob/format/tools/swift-format/Sources/Rules/UseWhereClausesInForLoops.swift)

1 Like

Thanks a lot. This should address the problem that I am working on. If I run into issues with this, will post here.

1 Like

I am now able to attach multiple statements within the then branch of an IfStmt using the following:

override func visit(_ node: CodeBlockItemListSyntax) -> Syntax {
   var newBody = SyntaxFactory.makeBlankCodeBlockItemList()
       for stmt in node {
         if let ifNode = stmt.item as? IfStmtSyntax {
            for s in ifNode.body.statements {
               newBody = newBody.appending(s)
            }
         } else {
            newBody = newBody.appending(stmt)
         }
      }
      return super.visit(newBody)
}

I had to use appending because CodeBlockListItemSyntax doesn't have a withBody unlike ForInStmtSyntax. But, the problem with removing the additional indentation due to if is still not fixed. It appears that the code transformation for using where clause that you pointed out also suffers from the same issue (copied the relevant parts and tried it out on a simple example, it removes the if statement but it leaves double indentation -- one for the for and the other for the if).

If there is a pointer to remove the addl. indentation, please let me know.

PS: When I copied your code, there was a compiler error due to the absence of lastToken in ExprSyntax. This is in let lastToken = node.sequenceExpr.lastToken

Unfortunately there’s no good way (right now) to remove the extra indentation — you’ll need to roll your own solution for that.

Thanks for catching my mistake in the code sample! You might have some more luck using a var newBody = [CodeBlockItem]() instead of appending to a CodeBlockItemList, and constructing one at the end using SyntaxFactory.makeCodeBlockItemList(newBody)

1 Like