swiftlang/swift-syntax

Body Macro formatted despite formatMode disabled

mateusrodriguesxyz opened this issue · 2 comments

Description

First level of nodes in body macro is always formatted even when formatMode is set to disabled.

Steps to Reproduce

Consider the following macro:

public struct SourceLocationMacro: BodyMacro {
    
    public static var formatMode: FormatMode { .disabled }
    
    public static func expansion(of node: AttributeSyntax, providingBodyFor declaration: some DeclSyntaxProtocol & WithOptionalCodeBlockSyntax, in context: some MacroExpansionContext) throws -> [CodeBlockItemSyntax] {
        
        if let statements = declaration.body?.statements {
            var body: [CodeBlockItemSyntax] = []
            statements.forEach { statement in
                if let location = context.location(of: statement, at: .afterLeadingTrivia, filePathMode: .filePath) {
                    let transformed = CodeBlockItemListSyntax {
                        "\n#sourceLocation(file: \(location.file), line: \(location.line))"
                        statement
                        "\n#sourceLocation()"
                    }
                    body.append(contentsOf: transformed)
                }
            }
            return body
        } else {
            return []
        }
        
    }
    
}

Usage example:

@SourceLocationMacro
func f() {
        let x: Int = 1
        ^ Immutable value 'x' was never used; consider replacing with '_' or removing it
}

// Macro Expansion:
{ 
    #sourceLocation(file: "/Users/mateusrodrigues/Desktop/TriviaMacro/Sources/TriviaMacroClient/main.swift", line: 8)
    let x: Int = 1
    #sourceLocation() }()
}

// Expected Macro Expansion:

{ 
#sourceLocation(file: "/Users/mateusrodrigues/Desktop/TriviaMacro/Sources/TriviaMacroClient/main.swift", line: 8)
        let x: Int = 1
#sourceLocation() }()
}

Discussion: https://forums.swift.org/t/body-macro-formatted-despite-formatmode-disabled/73488/3

Synced to Apple’s issue tracker as rdar://132597440

@ahoppen I've tracked the problem to two places:

return formatted.trimmedDescription(matching: { $0.isWhitespace })

Note that formattedExpansion is unconditionally trimming the formatted node, which removes any first-level indentation even when mode is .disabled.

func wrapInBraces() {
// Default to 4 spaces if no indentation was passed.
// In the future, we could consider inferring the indentation width from
// the expansions to collapse.
expansions = expansions.map({ $0.indented(by: indentationWidth ?? .spaces(4)) })
expansions[0] = "{\n" + expansions[0]
expansions[expansions.count - 1] += "\n}"
separator = "\n"
}

Here wrapInBraces is adding newlines and indentations regardless the macro formatMode.