diff --git a/Sources/SpyableMacro/Factories/ClosureFactory.swift b/Sources/SpyableMacro/Factories/ClosureFactory.swift index 2ab0bd4..df4e127 100644 --- a/Sources/SpyableMacro/Factories/ClosureFactory.swift +++ b/Sources/SpyableMacro/Factories/ClosureFactory.swift @@ -13,13 +13,13 @@ import SwiftSyntaxBuilder /// /// The following code: /// ```swift -/// var fooClosure: ((String, Int) async throws -> Data)? +/// var fooClosure: ((inout String, Int) async throws -> Data)? /// -/// try await fooClosure!(text, count) +/// try await fooClosure!(&text, count) /// ``` /// would be generated for a function like this: /// ```swift -/// func foo(text: String, count: Int) async throws -> Data +/// func foo(text: inout String, count: Int) async throws -> Data /// ``` /// and an argument `variablePrefix` equal to `foo`. /// @@ -81,20 +81,28 @@ struct ClosureFactory { ) } - var expression: ExprSyntaxProtocol = FunctionCallExprSyntax( - calledExpression: calledExpression, - leftParen: .leftParenToken(), - arguments: LabeledExprListSyntax { - for parameter in functionSignature.parameterClause.parameters { - let trailingTrivia: Trivia? = parameter.usesAutoclosure ? "()" : nil + let arguments = LabeledExprListSyntax { + for parameter in functionSignature.parameterClause.parameters { + let baseName = parameter.secondName ?? parameter.firstName + + if parameter.isInoutParameter { LabeledExprSyntax( - expression: DeclReferenceExprSyntax( - baseName: parameter.secondName ?? parameter.firstName - ), - trailingTrivia: trailingTrivia + expression: InOutExprSyntax( + expression: DeclReferenceExprSyntax(baseName: baseName) + ) ) + } else { + let trailingTrivia: Trivia? = parameter.usesAutoclosure ? "()" : nil + + LabeledExprSyntax(expression: DeclReferenceExprSyntax(baseName: baseName), trailingTrivia: trailingTrivia) } - }, + } + } + + var expression: ExprSyntaxProtocol = FunctionCallExprSyntax( + calledExpression: calledExpression, + leftParen: .leftParenToken(), + arguments: arguments, rightParen: .rightParenToken() ) @@ -113,3 +121,15 @@ struct ClosureFactory { TokenSyntax.identifier(variablePrefix + "Closure") } } + +extension FunctionParameterListSyntax.Element { + fileprivate var isInoutParameter: Bool { + if let attributedType = self.type.as(AttributedTypeSyntax.self), + attributedType.specifier?.text == TokenSyntax.keyword(.inout).text + { + return true + } else { + return false + } + } +} diff --git a/Sources/SpyableMacro/Factories/SpyFactory.swift b/Sources/SpyableMacro/Factories/SpyFactory.swift index d27ce7e..d48936d 100644 --- a/Sources/SpyableMacro/Factories/SpyFactory.swift +++ b/Sources/SpyableMacro/Factories/SpyFactory.swift @@ -60,7 +60,7 @@ import SwiftSyntaxBuilder /// } /// var fetchTextCountReceivedArguments: (text: String, count: Int)? /// var fetchTextCountReceivedInvocations: [(text: String, count: Int)] = [] -/// var fetchTextCountThrowableError: Error? +/// var fetchTextCountThrowableError: (any Error)? /// var fetchTextCountReturnValue: Decimal! /// var fetchTextCountClosure: ((String, Int) async throws -> Decimal)? /// diff --git a/Sources/SpyableMacro/Factories/ThrowableErrorFactory.swift b/Sources/SpyableMacro/Factories/ThrowableErrorFactory.swift index e2b38ea..770c6d2 100644 --- a/Sources/SpyableMacro/Factories/ThrowableErrorFactory.swift +++ b/Sources/SpyableMacro/Factories/ThrowableErrorFactory.swift @@ -13,7 +13,7 @@ import SwiftSyntaxBuilder /// /// The following code: /// ```swift -/// var fooThrowableError: Error? +/// var fooThrowableError: (any Error)? /// /// if let fooThrowableError { /// throw fooThrowableError @@ -32,7 +32,7 @@ struct ThrowableErrorFactory { func variableDeclaration(variablePrefix: String) throws -> VariableDeclSyntax { try VariableDeclSyntax( """ - var \(variableIdentifier(variablePrefix: variablePrefix)): Error? + var \(variableIdentifier(variablePrefix: variablePrefix)): (any Error)? """ ) } diff --git a/Tests/SpyableMacroTests/Factories/UT_ClosureFactory.swift b/Tests/SpyableMacroTests/Factories/UT_ClosureFactory.swift index 5f59e36..3438565 100644 --- a/Tests/SpyableMacroTests/Factories/UT_ClosureFactory.swift +++ b/Tests/SpyableMacroTests/Factories/UT_ClosureFactory.swift @@ -4,250 +4,157 @@ import XCTest @testable import SpyableMacro final class UT_ClosureFactory: XCTestCase { - func testVariableDeclaration() throws { - let variablePrefix = "foo" - let protocolFunctionDeclaration = try FunctionDeclSyntax( - """ - func foo() - """ - ) {} + // MARK: - Variable Declaration - let result = try ClosureFactory().variableDeclaration( - variablePrefix: variablePrefix, - functionSignature: protocolFunctionDeclaration.signature - ) - - assertBuildResult( - result, - """ - var fooClosure: (() -> Void)? - """ + func testVariableDeclaration() throws { + try assertProtocolFunction( + withFunctionDeclaration: "func _ignore_()", + prefixForVariable: "_prefix_", + expectingVariableDeclaration: "var _prefix_Closure: (() -> Void)?" ) } func testVariableDeclarationArguments() throws { - let variablePrefix = "foo" - - let protocolFunctionDeclaration = try FunctionDeclSyntax( - """ - func foo(text: String, count: UInt) - """ - ) {} - - let result = try ClosureFactory().variableDeclaration( - variablePrefix: variablePrefix, - functionSignature: protocolFunctionDeclaration.signature - ) - - assertBuildResult( - result, - """ - var fooClosure: ((String, UInt) -> Void)? - """ + try assertProtocolFunction( + withFunctionDeclaration: "func _ignore_(text: String, count: UInt)", + prefixForVariable: "_prefix_", + expectingVariableDeclaration: "var _prefix_Closure: ((String, UInt) -> Void)?" ) } func testVariableDeclarationAsync() throws { - let variablePrefix = "foo" - - let protocolFunctionDeclaration = try FunctionDeclSyntax( - """ - func foo() async - """ - ) {} - - let result = try ClosureFactory().variableDeclaration( - variablePrefix: variablePrefix, - functionSignature: protocolFunctionDeclaration.signature - ) - - assertBuildResult( - result, - """ - var fooClosure: (() async -> Void)? - """ + try assertProtocolFunction( + withFunctionDeclaration: "func _ignore_() async", + prefixForVariable: "_prefix_", + expectingVariableDeclaration: "var _prefix_Closure: (() async -> Void)?" ) } func testVariableDeclarationThrows() throws { - let variablePrefix = "foo" - - let protocolFunctionDeclaration = try FunctionDeclSyntax( - """ - func foo() throws - """ - ) {} - - let result = try ClosureFactory().variableDeclaration( - variablePrefix: variablePrefix, - functionSignature: protocolFunctionDeclaration.signature - ) - - assertBuildResult( - result, - """ - var fooClosure: (() throws -> Void)? - """ + try assertProtocolFunction( + withFunctionDeclaration: "func _ignore_() throws", + prefixForVariable: "_prefix_", + expectingVariableDeclaration: "var _prefix_Closure: (() throws -> Void)?" ) } func testVariableDeclarationReturnValue() throws { - let variablePrefix = "foo" - - let protocolFunctionDeclaration = try FunctionDeclSyntax( - """ - func foo() -> Data - """ - ) {} - - let result = try ClosureFactory().variableDeclaration( - variablePrefix: variablePrefix, - functionSignature: protocolFunctionDeclaration.signature + try assertProtocolFunction( + withFunctionDeclaration: "func _ignore_() -> Data", + prefixForVariable: "_prefix_", + expectingVariableDeclaration: "var _prefix_Closure: (() -> Data )?" ) + } - assertBuildResult( - result, - """ - var fooClosure: (() -> Data )? - """ + func testVariableDeclarationWithInoutAttribute() throws { + try assertProtocolFunction( + withFunctionDeclaration: "func _ignore_(value: inout String)", + prefixForVariable: "_prefix_", + expectingVariableDeclaration: "var _prefix_Closure: ((inout String) -> Void)?" ) } func testVariableDeclarationEverything() throws { - let variablePrefix = "foo" - - let protocolFunctionDeclaration = try FunctionDeclSyntax( - """ - func foo( - text: inout String, - product: (UInt?, name: String), - added: (() -> Void)?, - removed: @autoclosure @escaping () -> Bool - ) async throws -> (text: String, output: (() -> Void)?) - """ - ) {} - - let result = try ClosureFactory().variableDeclaration( - variablePrefix: variablePrefix, - functionSignature: protocolFunctionDeclaration.signature - ) - - assertBuildResult( - result, - """ - var fooClosure: ((inout String, (UInt?, name: String), (() -> Void)?, @autoclosure @escaping () -> Bool) async throws -> (text: String, output: (() -> Void)?) )? - """ + try assertProtocolFunction( + withFunctionDeclaration: """ + func _ignore_( + text: inout String, + product: (UInt?, name: String), + added: (() -> Void)?, + removed: @autoclosure @escaping () -> Bool + ) async throws -> (text: String, output: (() -> Void)?) + """, + prefixForVariable: "_prefix_", + expectingVariableDeclaration: """ + var _prefix_Closure: ((inout String, (UInt?, name: String), (() -> Void)?, @autoclosure @escaping () -> Bool) async throws -> (text: String, output: (() -> Void)?) )? + """ ) } - func testCallExpression() throws { - let variablePrefix = "foo" - - let protocolFunctionDeclaration = try FunctionDeclSyntax( - """ - func foo() - """ - ) {} + private func assertProtocolFunction( + withFunctionDeclaration functionDeclaration: String, + prefixForVariable variablePrefix: String, + expectingVariableDeclaration expectedDeclaration: String, + file: StaticString = #file, + line: UInt = #line + ) throws { + let protocolFunctionDeclaration = try FunctionDeclSyntax("\(raw: functionDeclaration)") {} - let result = ClosureFactory().callExpression( + let result = try ClosureFactory().variableDeclaration( variablePrefix: variablePrefix, functionSignature: protocolFunctionDeclaration.signature ) - assertBuildResult( - result, - """ - fooClosure?() - """ - ) + assertBuildResult(result, expectedDeclaration, file: file, line: line) } - func testCallExpressionArguments() throws { - let variablePrefix = "foo" - - let protocolFunctionDeclaration = try FunctionDeclSyntax( - """ - func foo(text: String, count: UInt) - """ - ) {} + // MARK: - Call Expression - let result = ClosureFactory().callExpression( - variablePrefix: variablePrefix, - functionSignature: protocolFunctionDeclaration.signature + func testCallExpression() throws { + try assertProtocolFunction( + withFunctionDeclaration: "func _ignore_()", + prefixForVariable: "_prefix_", + expectingCallExpression: "_prefix_Closure?()" ) + } - assertBuildResult( - result, - """ - fooClosure?(text, count) - """ + func testCallExpressionArguments() throws { + try assertProtocolFunction( + withFunctionDeclaration: "func _ignore_(text: String, count: UInt)", + prefixForVariable: "_prefix_", + expectingCallExpression: "_prefix_Closure?(text, count)" ) } func testCallExpressionAsync() throws { - let variablePrefix = "foo" - - let protocolFunctionDeclaration = try FunctionDeclSyntax( - """ - func foo() async - """ - ) {} - - let result = ClosureFactory().callExpression( - variablePrefix: variablePrefix, - functionSignature: protocolFunctionDeclaration.signature - ) - - assertBuildResult( - result, - """ - await fooClosure?() - """ + try assertProtocolFunction( + withFunctionDeclaration: "func _ignore_() async", + prefixForVariable: "_prefix_", + expectingCallExpression: "await _prefix_Closure?()" ) } func testCallExpressionThrows() throws { - let variablePrefix = "foo" - - let protocolFunctionDeclaration = try FunctionDeclSyntax( - """ - func foo() throws - """ - ) {} - - let result = ClosureFactory().callExpression( - variablePrefix: variablePrefix, - functionSignature: protocolFunctionDeclaration.signature + try assertProtocolFunction( + withFunctionDeclaration: "func _ignore_() throws", + prefixForVariable: "_prefix_", + expectingCallExpression: "try _prefix_Closure?()" ) + } - assertBuildResult( - result, - """ - try fooClosure?() - """ + func testCallExpressionWithInoutAttribute() throws { + try assertProtocolFunction( + withFunctionDeclaration: "func _ignore_(value: inout String)", + prefixForVariable: "_prefix_", + expectingCallExpression: "_prefix_Closure?(&value)" ) } func testCallExpressionEverything() throws { - let variablePrefix = "foo" + try assertProtocolFunction( + withFunctionDeclaration: """ + func _ignore_(text: inout String, product: (UInt?, name: String), added: (() -> Void)?, removed: @autoclosure @escaping () -> Bool) async throws -> String? + """, + prefixForVariable: "_prefix_", + expectingCallExpression: "try await _prefix_Closure!(&text, product, added, removed())" + ) + } - let protocolFunctionDeclaration = try FunctionDeclSyntax( - """ - func foo(text: inout String, product: (UInt?, name: String), added: (() -> Void)?, removed: @autoclosure @escaping () -> Bool) async throws -> String? - """ - ) {} + private func assertProtocolFunction( + withFunctionDeclaration functionDeclaration: String, + prefixForVariable variablePrefix: String, + expectingCallExpression expectedExpression: String, + file: StaticString = #file, + line: UInt = #line + ) throws { + let protocolFunctionDeclaration = try FunctionDeclSyntax("\(raw: functionDeclaration)") {} let result = ClosureFactory().callExpression( variablePrefix: variablePrefix, functionSignature: protocolFunctionDeclaration.signature ) - assertBuildResult( - result, - """ - try await fooClosure!(text, product, added, removed()) - """ - ) + assertBuildResult(result, expectedExpression, file: file, line: line) } } diff --git a/Tests/SpyableMacroTests/Factories/UT_SpyFactory.swift b/Tests/SpyableMacroTests/Factories/UT_SpyFactory.swift index 401a68c..12932fd 100644 --- a/Tests/SpyableMacroTests/Factories/UT_SpyFactory.swift +++ b/Tests/SpyableMacroTests/Factories/UT_SpyFactory.swift @@ -248,7 +248,7 @@ final class UT_SpyFactory: XCTestCase { } var fooReceivedAdded: ((text: String) -> Void)? var fooReceivedInvocations: [((text: String) -> Void)?] = [] - var fooThrowableError: Error? + var fooThrowableError: (any Error)? var fooReturnValue: (() -> Int)? var fooClosure: ((((text: String) -> Void)?) throws -> (() -> Int)?)? func foo(_ added: ((text: String) -> Void)?) throws -> (() -> Int)? { diff --git a/Tests/SpyableMacroTests/Factories/UT_ThrowableErrorFactory.swift b/Tests/SpyableMacroTests/Factories/UT_ThrowableErrorFactory.swift index ab928a6..84d95a7 100644 --- a/Tests/SpyableMacroTests/Factories/UT_ThrowableErrorFactory.swift +++ b/Tests/SpyableMacroTests/Factories/UT_ThrowableErrorFactory.swift @@ -12,7 +12,7 @@ final class UT_ThrowableErrorFactory: XCTestCase { assertBuildResult( result, """ - var functionNameThrowableError: Error? + var functionNameThrowableError: (any Error)? """ ) } diff --git a/Tests/SpyableMacroTests/Macro/UT_SpyableMacro.swift b/Tests/SpyableMacroTests/Macro/UT_SpyableMacro.swift index f6260e5..237e311 100644 --- a/Tests/SpyableMacroTests/Macro/UT_SpyableMacro.swift +++ b/Tests/SpyableMacroTests/Macro/UT_SpyableMacro.swift @@ -105,7 +105,7 @@ final class UT_SpyableMacro: XCTestCase { var fetchConfigCalled: Bool { return fetchConfigCallsCount > 0 } - var fetchConfigThrowableError: Error? + var fetchConfigThrowableError: (any Error)? var fetchConfigReturnValue: [String: String]! var fetchConfigClosure: (() async throws -> [String: String])? func fetchConfig() async throws -> [String: String] {