SwiftSyntax rewriter question

Hi Swift Community :)

Playing around with SwiftSyntax, I'm trying to remove public modifier from methods and variable declarations.

But was facing an issue where considering the following implementation

public class PublicModifierExtensionRewriter: SyntaxRewriter {
    
    override public func visit(_ node: FunctionDeclSyntax) -> DeclSyntax {
        guard let modifiers = node.modifiers else { return node }
        guard let extDecl = searchExtensionDeclParent(node: node), extDecl.isPublicExtension else {
            print("Is NOT declared on a public extension, so we don't need to remove")
            return node
        }
        if let publicModifier = modifiers.first(where: { $0.name.tokenKind == .publicKeyword }) {
            return node.withModifiers(modifiers.removing(childAt: publicModifier.indexInParent))
        }
        return node
    }
}

// Finding the Extension Decl parent. 
fileprivate func searchExtensionDeclParent(node: Syntax?) -> ExtensionDeclSyntax? {
    guard let node = node else { return nil }
    if let extensionDecl = node.parent as? ExtensionDeclSyntax {
        return extensionDecl
    } else {
        return searchExtensionDeclParent(node: node.parent)
    }    
}

And the following test file

public extension Int {
    public func f() -> Int {
        return self * self
    }
    static public func f2() {
    }

    public static func f3() {
    }
}

The actual result was

public extension Int {func f() -> Int {
        return self * self
    }
    static func f2() {
    }static func f3() {
    }
}

The problem is when the public modifier is the first modifier in FunctionDecl it end up rewriting the modifier but losing the leadingTrivia information from the FunctionDecl. But only when the public modifier is first, as you can notice on the example func f2() is fine because there is a static modifier before public. Also, tried instead of removing the modifier replacing with SyntaxFactory.makeBlankFunctionDecl() but the result was the same.

I know is very simple, but I'm not sure if its a bug on the rewriter or I'm missing something on the implementation.

I've end-up with a workaround by storing the leadingTrivia info from the original FunctionDecl and if the public modifier is the first, instead of removing it, just replace it with a ModifierDecl created whit the leadingTrivia. See here

I'm just curious to know if anyone also encounters that or something related and if there's a better way to do this or this is a bug.

Thank's in advance for the responses :))

It is behaving properly and your “workaround” is the right way to do it.

Conceptually, leading and trailing trivia (Trivia) are properties of tokens (TokenSyntax). Tokens in turn make up the larger derived syntax nodes. The leading trivia property of any larger syntax node is really just a convenience property that forwards to the leading trivia of its first token. Ergo, removing the first token as you have done involves removing the trivia it “contains”.

A well‐designed convenience method that follows the semantics you originally expected might be accepted as a new enhancement. I think @akyrtzi would be the one to bring it up with.

1 Like

Uhumm... so that's why we can only add trivia to a TokenSyntax and not to a decl. That makes total sense now, thank you so much for the explanation @SDGGiesbrecht :slight_smile: