Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inherit protocol modifiers #80

Closed
wants to merge 12 commits into from
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import SwiftSyntax

extension VariableDeclSyntax {
func applying(modifiers: DeclModifierListSyntax) -> VariableDeclSyntax {
var copy = self
copy.modifiers = modifiers
return copy
}
}
5 changes: 4 additions & 1 deletion Sources/SpyableMacro/Factories/CalledFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,16 @@ import SwiftSyntaxBuilder
/// ```
/// and an argument `variablePrefix` equal to `foo`.
struct CalledFactory {
func variableDeclaration(variablePrefix: String) throws -> VariableDeclSyntax {
func variableDeclaration(modifiers: DeclModifierListSyntax, variablePrefix: String) throws
-> VariableDeclSyntax
{
try VariableDeclSyntax(
"""
var \(raw: variablePrefix)Called: Bool {
return \(raw: variablePrefix)CallsCount > 0
}
"""
)
.applying(modifiers: modifiers)
}
}
5 changes: 4 additions & 1 deletion Sources/SpyableMacro/Factories/CallsCountFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,15 @@ import SwiftSyntaxBuilder
/// ```
/// and an argument `variablePrefix` equal to `foo`.
struct CallsCountFactory {
func variableDeclaration(variablePrefix: String) throws -> VariableDeclSyntax {
func variableDeclaration(modifiers: DeclModifierListSyntax, variablePrefix: String) throws
-> VariableDeclSyntax
{
try VariableDeclSyntax(
"""
var \(variableIdentifier(variablePrefix: variablePrefix)) = 0
"""
)
.applying(modifiers: modifiers)
}

func incrementVariableExpression(variablePrefix: String) -> ExprSyntax {
Expand Down
2 changes: 2 additions & 0 deletions Sources/SpyableMacro/Factories/ClosureFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import SwiftSyntaxBuilder
/// interacts correctly with the function.
struct ClosureFactory {
func variableDeclaration(
modifiers: DeclModifierListSyntax,
variablePrefix: String,
functionSignature: FunctionSignatureSyntax
) throws -> VariableDeclSyntax {
Expand Down Expand Up @@ -59,6 +60,7 @@ struct ClosureFactory {
var \(variableIdentifier(variablePrefix: variablePrefix)): (\(elements))?
"""
)
.applying(modifiers: modifiers)
}

func callExpression(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,20 @@ struct FunctionImplementationFactory {
private let returnValueFactory = ReturnValueFactory()

func declaration(
modifiers: DeclModifierListSyntax,
variablePrefix: String,
protocolFunctionDeclaration: FunctionDeclSyntax
) -> FunctionDeclSyntax {
var spyFunctionDeclaration = protocolFunctionDeclaration

spyFunctionDeclaration.modifiers = protocolFunctionDeclaration.modifiers.removingMutatingKeyword
spyFunctionDeclaration.modifiers = DeclModifierListSyntax {
for protoModifier in modifiers {
protoModifier
}
for functionModifier in protocolFunctionDeclaration.modifiers.removingMutatingKeyword {
functionModifier
}
}

spyFunctionDeclaration.body = CodeBlockSyntax {
let parameterList = protocolFunctionDeclaration.signature.parameterClause.parameters
Expand Down
2 changes: 2 additions & 0 deletions Sources/SpyableMacro/Factories/ReceivedArgumentsFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import SwiftSyntaxBuilder
/// and an argument `variablePrefix` equal to `bar`.
struct ReceivedArgumentsFactory {
func variableDeclaration(
modifiers: DeclModifierListSyntax,
variablePrefix: String,
parameterList: FunctionParameterListSyntax
) throws -> VariableDeclSyntax {
Expand All @@ -51,6 +52,7 @@ struct ReceivedArgumentsFactory {
var \(identifier): \(type)
"""
)
.applying(modifiers: modifiers)
}

private func variableType(parameterList: FunctionParameterListSyntax) -> TypeSyntaxProtocol {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import SwiftSyntaxBuilder
/// about the arguments in the last invocation, use `ReceivedArgumentsFactory`.
struct ReceivedInvocationsFactory {
func variableDeclaration(
modifiers: DeclModifierListSyntax,
variablePrefix: String,
parameterList: FunctionParameterListSyntax
) throws -> VariableDeclSyntax {
Expand All @@ -53,6 +54,7 @@ struct ReceivedInvocationsFactory {
var \(identifier): [\(elementType)] = []
"""
)
.applying(modifiers: modifiers)
}

private func arrayElementType(parameterList: FunctionParameterListSyntax) -> TypeSyntaxProtocol {
Expand Down
2 changes: 2 additions & 0 deletions Sources/SpyableMacro/Factories/ReturnValueFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import SwiftSyntaxBuilder
/// correctly to different returned values.
struct ReturnValueFactory {
func variableDeclaration(
modifiers: DeclModifierListSyntax,
variablePrefix: String,
functionReturnType: TypeSyntax
) throws -> VariableDeclSyntax {
Expand All @@ -56,6 +57,7 @@ struct ReturnValueFactory {
var \(variableIdentifier(variablePrefix: variablePrefix))\(typeAnnotation)
"""
)
.applying(modifiers: modifiers)
}

func returnStatement(variablePrefix: String) -> StmtSyntax {
Expand Down
37 changes: 34 additions & 3 deletions Sources/SpyableMacro/Factories/SpyFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,10 @@ struct SpyFactory {
let functionDeclarations = protocolDeclaration.memberBlock.members
.compactMap { $0.decl.as(FunctionDeclSyntax.self)?.removingLeadingSpaces }

let memberModifiers = protocolDeclaration.modifiers.replacingPrivateWithFileprivate

return try ClassDeclSyntax(
modifiers: protocolDeclaration.modifiers,
name: identifier,
genericParameterClause: genericParameterClause,
inheritanceClause: InheritanceClauseSyntax {
Expand All @@ -116,8 +119,15 @@ struct SpyFactory {
)
},
memberBlockBuilder: {
InitializerDeclSyntax(
modifiers: memberModifiers,
signature: FunctionSignatureSyntax(parameterClause: .init(parameters: [])),
body: CodeBlockSyntax(statements: [])
)

for variableDeclaration in variableDeclarations {
try variablesImplementationFactory.variablesDeclarations(
modifiers: memberModifiers,
protocolVariableDeclaration: variableDeclaration
)
}
Expand All @@ -126,37 +136,45 @@ struct SpyFactory {
let variablePrefix = variablePrefixFactory.text(for: functionDeclaration)
let parameterList = functionDeclaration.signature.parameterClause.parameters

try callsCountFactory.variableDeclaration(variablePrefix: variablePrefix)
try calledFactory.variableDeclaration(variablePrefix: variablePrefix)
try callsCountFactory.variableDeclaration(
modifiers: memberModifiers, variablePrefix: variablePrefix)
try calledFactory.variableDeclaration(
modifiers: memberModifiers, variablePrefix: variablePrefix)

if parameterList.supportsParameterTracking {
try receivedArgumentsFactory.variableDeclaration(
modifiers: memberModifiers,
variablePrefix: variablePrefix,
parameterList: parameterList
)
try receivedInvocationsFactory.variableDeclaration(
modifiers: memberModifiers,
variablePrefix: variablePrefix,
parameterList: parameterList
)
}

if functionDeclaration.signature.effectSpecifiers?.throwsSpecifier != nil {
try throwableErrorFactory.variableDeclaration(variablePrefix: variablePrefix)
try throwableErrorFactory.variableDeclaration(
modifiers: memberModifiers, variablePrefix: variablePrefix)
}

if let returnType = functionDeclaration.signature.returnClause?.type {
try returnValueFactory.variableDeclaration(
modifiers: memberModifiers,
variablePrefix: variablePrefix,
functionReturnType: returnType
)
}

try closureFactory.variableDeclaration(
modifiers: memberModifiers,
variablePrefix: variablePrefix,
functionSignature: functionDeclaration.signature
)

functionImplementationFactory.declaration(
modifiers: memberModifiers,
variablePrefix: variablePrefix,
protocolFunctionDeclaration: functionDeclaration
)
Expand Down Expand Up @@ -185,3 +203,16 @@ extension SyntaxProtocol {
)
}
}

extension DeclModifierListSyntax {
fileprivate var replacingPrivateWithFileprivate: Self {
DeclModifierListSyntax(
map {
if $0.name.text == TokenSyntax.keyword(.private).text {
return .init(name: .keyword(.fileprivate))
} else {
return $0
}
})
}
}
5 changes: 4 additions & 1 deletion Sources/SpyableMacro/Factories/ThrowableErrorFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,15 @@ import SwiftSyntaxBuilder
/// your tests. You can use it to simulate different scenarios and verify that your code handles
/// errors correctly.
struct ThrowableErrorFactory {
func variableDeclaration(variablePrefix: String) throws -> VariableDeclSyntax {
func variableDeclaration(modifiers: DeclModifierListSyntax, variablePrefix: String) throws
-> VariableDeclSyntax
{
try VariableDeclSyntax(
"""
var \(variableIdentifier(variablePrefix: variablePrefix)): (any Error)?
"""
)
.applying(modifiers: modifiers)
}

func throwErrorExpression(variablePrefix: String) -> ExprSyntax {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,18 +47,23 @@ struct VariablesImplementationFactory {

@MemberBlockItemListBuilder
func variablesDeclarations(
modifiers: DeclModifierListSyntax,
protocolVariableDeclaration: VariableDeclSyntax
) throws -> MemberBlockItemListSyntax {
if protocolVariableDeclaration.bindings.count == 1 {
// Since the count of `bindings` is exactly 1, it is safe to force unwrap it.
let binding = protocolVariableDeclaration.bindings.first!

if binding.typeAnnotation?.type.is(OptionalTypeSyntax.self) == true {
accessorRemovalVisitor.visit(protocolVariableDeclaration)
if let variableDecl = accessorRemovalVisitor.visit(protocolVariableDeclaration).as(
VariableDeclSyntax.self)
{
variableDecl.applying(modifiers: modifiers)
}
} else {
try protocolVariableDeclarationWithGetterAndSetter(binding: binding)
try protocolVariableDeclarationWithGetterAndSetter(modifiers: modifiers, binding: binding)

try underlyingVariableDeclaration(binding: binding)
try underlyingVariableDeclaration(modifiers: modifiers, binding: binding)
}
} else {
// As far as I know variable declaration in a protocol should have exactly one binding.
Expand All @@ -67,6 +72,7 @@ struct VariablesImplementationFactory {
}

private func protocolVariableDeclarationWithGetterAndSetter(
modifiers: DeclModifierListSyntax,
binding: PatternBindingSyntax
) throws -> VariableDeclSyntax {
try VariableDeclSyntax(
Expand All @@ -77,16 +83,19 @@ struct VariablesImplementationFactory {
}
"""
)
.applying(modifiers: modifiers)
}

private func underlyingVariableDeclaration(
modifiers: DeclModifierListSyntax,
binding: PatternBindingListSyntax.Element
) throws -> VariableDeclSyntax {
try VariableDeclSyntax(
"""
var \(raw: underlyingVariableName(binding: binding)): (\(binding.typeAnnotation!.type.trimmed))!
"""
)
.applying(modifiers: modifiers)
}

private func underlyingVariableName(binding: PatternBindingListSyntax.Element) throws -> String {
Expand Down
19 changes: 18 additions & 1 deletion Tests/SpyableMacroTests/Factories/UT_CalledFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ final class UT_CalledFactory: XCTestCase {
func testVariableDeclaration() throws {
let variablePrefix = "functionName"

let result = try CalledFactory().variableDeclaration(variablePrefix: variablePrefix)
let result = try CalledFactory().variableDeclaration(
modifiers: [], variablePrefix: variablePrefix)

assertBuildResult(
result,
Expand All @@ -21,4 +22,20 @@ final class UT_CalledFactory: XCTestCase {
"""
)
}

func testVariableDeclarationWithAccess() throws {
let variablePrefix = "functionName"

let result = try CalledFactory().variableDeclaration(
modifiers: [.init(name: "public")], variablePrefix: variablePrefix)

assertBuildResult(
result,
"""
public var functionNameCalled: Bool {
return functionNameCallsCount > 0
}
"""
)
}
}
17 changes: 16 additions & 1 deletion Tests/SpyableMacroTests/Factories/UT_CallsCountFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ final class UT_CallsCountFactory: XCTestCase {
func testVariableDeclaration() throws {
let variablePrefix = "functionName"

let result = try CallsCountFactory().variableDeclaration(variablePrefix: variablePrefix)
let result = try CallsCountFactory().variableDeclaration(
modifiers: [], variablePrefix: variablePrefix)

assertBuildResult(
result,
Expand All @@ -20,6 +21,20 @@ final class UT_CallsCountFactory: XCTestCase {
)
}

func testVariableDeclarationWithAccess() throws {
let variablePrefix = "functionName"

let result = try CallsCountFactory().variableDeclaration(
modifiers: [.init(name: "public")], variablePrefix: variablePrefix)

assertBuildResult(
result,
"""
public var functionNameCallsCount = 0
"""
)
}

// MARK: - Variable Expression

func testIncrementVariableExpression() {
Expand Down
1 change: 1 addition & 0 deletions Tests/SpyableMacroTests/Factories/UT_ClosureFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ final class UT_ClosureFactory: XCTestCase {
let protocolFunctionDeclaration = try FunctionDeclSyntax("\(raw: functionDeclaration)") {}

let result = try ClosureFactory().variableDeclaration(
modifiers: [],
variablePrefix: variablePrefix,
functionSignature: protocolFunctionDeclaration.signature
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,19 @@ final class UT_FunctionImplementationFactory: XCTestCase {
)
}

func testDeclarationAccessModifiers() throws {
try assertProtocolFunction(
withFunctionDeclaration: "public func foo()",
prefixForVariable: "_prefix_",
expectingFunctionDeclaration: """
public func foo() {
_prefix_CallsCount += 1
_prefix_Closure?()
}
"""
)
}

func testDeclarationArguments() throws {
try assertProtocolFunction(
withFunctionDeclaration: "func foo(text: String, count: Int)",
Expand Down Expand Up @@ -129,6 +142,7 @@ final class UT_FunctionImplementationFactory: XCTestCase {
let protocolFunctionDeclaration = try FunctionDeclSyntax("\(raw: functionDeclaration)") {}

let result = FunctionImplementationFactory().declaration(
modifiers: [],
variablePrefix: variablePrefix,
protocolFunctionDeclaration: protocolFunctionDeclaration
)
Expand Down
Loading