I am writing a method that runs a closure and measure how long it took the code to run. In the output, I want to include the actual source code that is run, so the method takes two parameters like this:
func measure(_ block: () -> T, _ blockDescription: StaticString) -> T {
let start = Date()
let result = block()
let duration = Date().timeIntervalSince(start)
print( // I'm outputting to some markdown file, but for simplicity's sake let's just use 'print'
"""
```swift
\(blockDescription)
```
took \(duration) seconds.
"""
)
return result
}
The intended usage is like this:
measure({
doSomething()
doSomethingElse()
}, """
doSomething()
doSomethingElse()
""")
Of course, manually repeating the statements in the closure is not maintainable at all, so I tried to write a freestanding expression macro to do this. The macro will find the closure, and convert that to a StringLiteralExprSyntax. As a result, I only need to do
#measure {
doSomething()
doSomethingElse()
}
Here is my implementation:
enum MeasureMacro: ExpressionMacro {
static func expansion(
of node: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext
) throws -> ExprSyntax {
let closure = if let firstArg = node.arguments.first?.expression.as(ClosureExprSyntax.self) {
firstArg
} else if let trailingCLosure = node.trailingClosure {
trailingCLosure
} else {
throw MacroExpansionErrorMessage("Missing closure")
}
let closureString = closure.statements.trimmedDescription
let argList = LabeledExprListSyntax {
LabeledExprSyntax(expression: closure)
LabeledExprSyntax(expression: StringLiteralExprSyntax(content: closureString))
}
return "measure(\(argList))"
}
}
I thought using trimmedDescription would remove all the extra indentation, but it only does it for the first line. It ends up generating the string literal:
"doSomething()\n doSomethingElse()"
whereas I wanted
"doSomething()\ndoSomethingElse()"
I tried adding .formatted() before trimmedDescription, but that does not change anything.
How can I remove the indentation?