Skip to content

Commit c2633b1

Browse files
committed
fix: automockable macro
1 parent 065f4eb commit c2633b1

16 files changed

+622
-422
lines changed

CHANGELOG.md

+18
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,24 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
# [0.2.2] - 2024-11-06
9+
10+
### Fixed
11+
12+
- Allow Automackable to support more cases
13+
14+
# [0.2.1] - 2024-10-23
15+
16+
### Fixed
17+
18+
- Fix unsuported optionals to StringOrInt and StringOrDouble macros
19+
20+
# [0.2.0] - 2024-10-23
21+
22+
### Added
23+
24+
- StringToDouble macro
25+
-
826
# [0.1.1] - 2024-10-21
927

1028
### Fixed

Sources/SageSwiftKit/CodableMacros.swift

-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// Copyright © 2024 Sage.
33
// All Rights Reserved.
44

5-
65
import Foundation
76

87
@attached(peer)

Sources/SageSwiftKit/JsonMockable.swift

-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
import Foundation
66

7-
87
@attached(member, names: arbitrary)
98
public macro JsonMockable(
109
keyDecodingStrategy: JSONDecoder.KeyDecodingStrategy,

Sources/SageSwiftKit/SageSwiftKit.swift

-1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,5 @@ public macro CustomHashable(parameters: [String]) = #externalMacro(module: "Sage
2828
@attached(extension, conformances: Equatable, names: arbitrary)
2929
public macro CustomEquatable(parameters: [String]) = #externalMacro(module: "SageSwiftKitMacros", type: "CustomEquatable")
3030

31-
3231
@attached(member, names: arbitrary)
3332
public macro NodePrinter() = #externalMacro(module: "SageSwiftKitMacros", type: "NodePrinter")

Sources/SageSwiftKitClient/main.swift

-29
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,6 @@ import SageSwiftKit
55
import Foundation
66
import Combine
77

8-
@AutoMockable(accessLevel: "public")
9-
protocol FooProtocol {
10-
func tmpFunc(var1: @escaping (String?) -> Void, var2: AnyPublisher<Bool, Never>) -> Void
11-
}
12-
13-
let mockProtocol: FooProtocolMock = .init()
14-
158
struct SubObject: Codable {
169
let id: Int
1710
let name: String
@@ -42,25 +35,3 @@ struct PlayingObject {
4235

4336
var identifier: Int
4437
}
45-
46-
47-
@JsonMockable(
48-
keyDecodingStrategy: .convertFromSnakeCase,
49-
bundle: .main
50-
)
51-
struct User: Decodable {
52-
var example: String
53-
54-
static var mockA: Self {
55-
get throws {
56-
try getMock(bundle: .main, keyDecodingStrategy: .convertFromSnakeCase, fileName: "fileA")
57-
}
58-
}
59-
60-
static var mockB: Self {
61-
get throws {
62-
try getMock(bundle: .main, keyDecodingStrategy: .convertFromSnakeCase, fileName: "fileB")
63-
}
64-
}
65-
66-
}

Sources/SageSwiftKitMacros/JsonMockable/Macros/JsonMockable.swift

+1-3
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,7 @@ public enum JsonMockable: MemberMacro {
5959
)
6060

6161
declSyntax.append(DeclSyntax(function))
62-
63-
64-
62+
6563
let variable = try VariableDeclSyntax(
6664
"public static var jsonMock: \(name.tokenSyntax)") {
6765
try .init(itemsBuilder: {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
//
2+
// Copyright © 2024 Sage.
3+
// All Rights Reserved.
4+
5+
import Foundation
6+
import SwiftSyntax
7+
import SwiftSyntaxMacros
8+
import SwiftSyntaxBuilder
9+
10+
public enum AutoMockable: PeerMacro {
11+
static var mocksVarName: String { "mock" }
12+
13+
public static func expansion(
14+
of node: AttributeSyntax,
15+
providingPeersOf declaration: some DeclSyntaxProtocol,
16+
in context: some MacroExpansionContext
17+
) throws -> [DeclSyntax] {
18+
guard let protocolSyntax = declaration.as(ProtocolDeclSyntax.self) else {
19+
return []
20+
}
21+
22+
let accessLevel = node
23+
.adapter
24+
.findArgument(id: "accessLevel")?
25+
.adapter
26+
.expression(cast: StringLiteralExprSyntax.self)?.representedLiteralValue ?? "internal"
27+
28+
let procotolName = protocolSyntax.name.text
29+
30+
guard let members = declaration.as(ProtocolDeclSyntax.self)?.memberBlock.members else {
31+
return []
32+
}
33+
34+
let variablesToMock: [VariableDeclSyntax] = members.compactMap { $0.decl.as(VariableDeclSyntax.self) }
35+
36+
let functionsToMock: [FunctionsMockData] = members
37+
.compactMap { item -> FunctionsMockData? in
38+
guard let casted = item.decl.as(FunctionDeclSyntax.self) else {
39+
return nil
40+
}
41+
42+
return FunctionsMockData(syntax: casted, accessLevel: accessLevel.tokenSyntax)
43+
}
44+
45+
return [
46+
DeclSyntax(
47+
ClassDeclSyntax(
48+
modifiers: .init(itemsBuilder: {
49+
DeclModifierSyntax(name: accessLevel.tokenSyntax)
50+
}),
51+
name: .identifier("\(procotolName)Mock"),
52+
inheritanceClause: .init(
53+
inheritedTypes: .init(itemsBuilder: {
54+
InheritedTypeSyntax(
55+
type: IdentifierTypeSyntax(
56+
name: .identifier(procotolName)
57+
)
58+
)
59+
})
60+
),
61+
memberBlock: MemberBlockSyntax(
62+
members: try MemberBlockItemListSyntax(itemsBuilder: {
63+
// Classes that has mock data for each function
64+
for funcData in functionsToMock {
65+
ClassMockForFunctionBuilder(funcData: funcData).build()
66+
}
67+
68+
// Class with all functions mock
69+
FunctionMocksClassBuilder(
70+
functions: functionsToMock,
71+
accessLevel: accessLevel.tokenSyntax
72+
).build()
73+
74+
// Variable mocks
75+
FunctionMocksClassBuilder(
76+
functions: functionsToMock,
77+
accessLevel: accessLevel.tokenSyntax
78+
).buildVarForTheClass()
79+
80+
// Implementation of each variable
81+
for variable in variablesToMock {
82+
let varConformance = ProtocolVarsConformanceBuilder(
83+
variable: variable,
84+
accessLevel: accessLevel.tokenSyntax
85+
)
86+
87+
varConformance.build()
88+
varConformance.buildReturnVar()
89+
}
90+
91+
// Implementation of each function
92+
for data in functionsToMock {
93+
try ProtocolFunctionsConformanceBuilder(
94+
data: data
95+
).build()
96+
}
97+
})
98+
)
99+
)
100+
)
101+
]
102+
}
103+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
//
2+
// Copyright © 2024 Sage.
3+
// All Rights Reserved.
4+
5+
import Foundation
6+
import SwiftSyntax
7+
import SwiftSyntaxMacros
8+
import SwiftSyntaxBuilder
9+
10+
// Creates the mock for each function with its parameters calls retun value etc...
11+
struct ClassMockForFunctionBuilder {
12+
let funcData: FunctionsMockData
13+
14+
var parametersName: String { "Parameters" }
15+
var callsName: String { "calls" }
16+
17+
init(funcData: FunctionsMockData) {
18+
self.funcData = funcData
19+
}
20+
21+
func build() -> ClassDeclSyntax {
22+
ClassDeclSyntax(
23+
modifiers: .init(
24+
itemsBuilder: {
25+
.init(name: funcData.accessLevel)
26+
}
27+
),
28+
name: funcData.className.tokenSyntax,
29+
memberBlock: .init(
30+
members: .init(
31+
itemsBuilder: {
32+
if let parametersStruct = buildParameters() {
33+
parametersStruct
34+
}
35+
36+
buildCalls()
37+
buildLastCall()
38+
39+
buildCalled()
40+
41+
if let returnValue = funcData.returnValue {
42+
VariableDeclSyntax(
43+
modifiers: .init(itemsBuilder: {
44+
.init(name: funcData.accessLevel)
45+
}),
46+
Keyword.var,
47+
name: "returnValue",
48+
type: TypeAnnotationSyntax(
49+
type: TypeSyntax(stringLiteral: returnValue)
50+
)
51+
)
52+
}
53+
54+
InitializerDeclSyntax(
55+
signature: .init(parameterClause: .init(
56+
parameters: .init(itemsBuilder: {
57+
58+
})
59+
)),
60+
body: .init(statements: .init(itemsBuilder: {
61+
62+
}))
63+
)
64+
}
65+
)
66+
)
67+
)
68+
}
69+
}
70+
71+
extension ClassMockForFunctionBuilder {
72+
func buildParameters() -> StructDeclSyntax? {
73+
return StructDeclSyntax(
74+
modifiers: .init(itemsBuilder: {
75+
.init(name: funcData.accessLevel)
76+
}),
77+
name: parametersName.tokenSyntax,
78+
memberBlock: .init(members: .init(itemsBuilder: {
79+
for parameter in funcData.params {
80+
VariableDeclSyntax(
81+
modifiers: .init(itemsBuilder: {
82+
.init(name: funcData.accessLevel)
83+
}),
84+
Keyword.let,
85+
name: PatternSyntax(stringLiteral: parameter.name),
86+
type: TypeAnnotationSyntax(
87+
type: TypeSyntax(stringLiteral: parameter.noEscapingType)
88+
)
89+
)
90+
}
91+
}))
92+
)
93+
}
94+
95+
func buildCalls() -> VariableDeclSyntax {
96+
return VariableDeclSyntax(
97+
modifiers: .init(itemsBuilder: {
98+
.init(name: funcData.accessLevel)
99+
}),
100+
Keyword.var,
101+
name: .init(stringLiteral: callsName),
102+
type: TypeAnnotationSyntax(
103+
type: TypeSyntax(stringLiteral: "[\(parametersName)]")
104+
),
105+
initializer: InitializerClauseSyntax(
106+
value: ArrayExprSyntax(elements: .init(itemsBuilder: {
107+
108+
}))
109+
)
110+
)
111+
}
112+
113+
func buildCalled() -> VariableDeclSyntax {
114+
return VariableDeclSyntax(
115+
modifiers: .init(itemsBuilder: {
116+
.init(name: funcData.accessLevel)
117+
}),
118+
bindingSpecifier: .keyword(.var),
119+
bindings: .init(itemsBuilder: {
120+
PatternBindingSyntax(
121+
pattern: IdentifierPatternSyntax(identifier: "called"),
122+
typeAnnotation: TypeAnnotationSyntax(type: IdentifierTypeSyntax(name: "Bool")),
123+
accessorBlock: AccessorBlockSyntax(
124+
leftBrace: .leftBraceToken(),
125+
accessors: .init(CodeBlockItemListSyntax(itemsBuilder: {
126+
ReturnStmtSyntax(
127+
returnKeyword: .keyword(.return),
128+
expression: DeclReferenceExprSyntax(
129+
baseName: "self.lastCall != nil".tokenSyntax
130+
)
131+
)
132+
})),
133+
rightBrace: .rightBraceToken()
134+
)
135+
)
136+
})
137+
)
138+
}
139+
140+
func buildLastCall() -> VariableDeclSyntax {
141+
return VariableDeclSyntax(
142+
modifiers: .init(itemsBuilder: {
143+
.init(name: funcData.accessLevel)
144+
}),
145+
bindingSpecifier: .keyword(.var),
146+
bindings: .init(itemsBuilder: {
147+
PatternBindingSyntax(
148+
pattern: IdentifierPatternSyntax(identifier: "lastCall"),
149+
typeAnnotation: TypeAnnotationSyntax(
150+
type: OptionalTypeSyntax(
151+
wrappedType: IdentifierTypeSyntax(name: parametersName.tokenSyntax)
152+
)
153+
),
154+
accessorBlock: AccessorBlockSyntax(
155+
leftBrace: .leftBraceToken(),
156+
accessors: .init(CodeBlockItemListSyntax(itemsBuilder: {
157+
ReturnStmtSyntax(
158+
returnKeyword: .keyword(.return),
159+
expression: DeclReferenceExprSyntax(
160+
baseName: "self.calls.last".tokenSyntax
161+
)
162+
)
163+
})),
164+
rightBrace: .rightBraceToken()
165+
)
166+
)
167+
})
168+
)
169+
}
170+
}

0 commit comments

Comments
 (0)