I used SwiftSyntax in my SceneGen tool. The syntax code can easily get messy and hard to follow, so I’ve been following these patterns in my renderer code to keep it neat and simple:

Prepare the Data First

Process any data that the renderer functions will need separately from any code that touches SwiftSyntax. Any fetching, parsing, mapping should be done outside the renderer. The renderer functions should just take a model with everything neatly arranged for it and output the rendered code.

This helps keep this code simpler, because it’s only about the structure of the source code you’re building.

Use Extensions to Simplify

If you find you have a few lines of SwiftSyntax code just to do something very simple, then extract it into an extension, especially if it’s repeated in a few places.

Make these extensions private, they should be specific to your implementation here and won’t usually be re-usable in the rest of the project.

When writing extensions consider using concrete types for the arguments, like TypeSyntax, instead of protocols like TypeSyntaxProtocol. This will allow you to leverage ExpressibleByStringLiteral so you can just pass a string at the call-site.

Here’s an example — I often want to tag a struct as conforming to a protocol, so I added this extension, and now I can call ExtensionDeclSyntax.conform("MyStruct", to: "Equatable") to do it in one line of easily understood code.

/// Adds a conformance to a type
/// Usage: `ExtensionDeclSyntax.conform("MyStruct", to: "Equatable")`
private extension ExtensionDeclSyntax {
  static func conform(
    _ type: TypeSyntax, 
    to conformType: TypeSyntax
  ) -> ExtensionDeclSyntax {
    ExtensionDeclSyntax(
      leadingTrivia: .newline,
      extendedType: type,
      inheritanceClause: InheritanceClauseSyntax(
        inheritedTypes: [InheritedTypeSyntax(type: conformType)]
      )
    ) {}
  }
}

Isolate SwiftSyntax to One File

Keep your SwiftSyntax quarantined. For public functions, don’t use any SwiftSyntax types as parameters or return values. I have a single ~250 line file with import SwiftSyntax in my project and it contains everything I need to understand how rendering is working.

Find a Balance when Specifying Syntax

Don’t go to extremes to specify everything as granularly as possible. When rendering a utility function with no interpolation in it, favour using DeclSyntax() with a block string.

Conversely, if you have big block string with a lot of interpolation happening, consider breaking it up into more explicit pieces of syntax.