Skip to content

Commit 59cbddf

Browse files
authored
Add protocol-level @available annotation inheritance to generated mocks (#315)
* Add protocol-level @available annotation inheritance to generated mocks * Fix unintended formatting changes introduced during conflict resolution * Add tests for protocol-level @available inheritance on Sendable mocks * Add availability-gated Sendable fixture coverage * Remove redundant ParsedEntity attribute comment * Fix availability attribute extraction for mock generation * Add availability test for inherited protocols
1 parent 7ba3ff8 commit 59cbddf

5 files changed

Lines changed: 99 additions & 6 deletions

File tree

Sources/MockoloFramework/Models/ParsedEntity.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,13 @@ struct ResolvedEntity {
6565

6666
func model() -> Model {
6767
let metadata = entity.metadata
68+
let combinedAttributes = entity.entityNode.attributeDescriptions + attributes
6869
return NominalModel(selfType: .init(name: metadata?.nameOverride ?? (key + "Mock")),
6970
namespaces: entity.entityNode.namespaces,
7071
acl: entity.entityNode.accessLevel,
7172
declKindOfMockAnnotatedBaseType: entity.entityNode.declKind,
7273
declKind: inheritsActorProtocol ? .actor : .class,
73-
attributes: attributes,
74+
attributes: combinedAttributes,
7475
offset: entity.entityNode.offset,
7576
inheritedTypeName: (entity.metadata?.module?.withDot ?? "") + key,
7677
genericWhereConstraints: entity.entityNode.genericWhereConstraints,
@@ -91,7 +92,7 @@ protocol EntityNode {
9192
var nameText: String { get }
9293
var mayHaveGlobalActor: Bool { get }
9394
var accessLevel: String { get }
94-
var attributesDescription: String { get }
95+
var attributeDescriptions: [String] { get }
9596
var declKind: NominalTypeDeclKind { get }
9697
var inheritedTypes: [String] { get }
9798
var genericWhereConstraints: [String] { get }

Sources/MockoloFramework/Parsers/SwiftSyntaxExtensions.swift

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -303,8 +303,8 @@ extension ProtocolDeclSyntax: EntityNode {
303303
return genericWhereClause?.requirements.map { $0.with(\.trailingComma, nil).trimmedDescription } ?? []
304304
}
305305

306-
var attributesDescription: String {
307-
self.attributes.trimmedDescription
306+
var attributeDescriptions: [String] {
307+
return attributes.descriptions
308308
}
309309

310310
func annotationMetadata(with annotation: String) -> AnnotationMetadata? {
@@ -354,8 +354,8 @@ extension ClassDeclSyntax: EntityNode {
354354
return genericWhereClause?.requirements.map { $0.with(\.trailingComma, nil).trimmedDescription } ?? []
355355
}
356356

357-
var attributesDescription: String {
358-
self.attributes.trimmedDescription
357+
var attributeDescriptions: [String] {
358+
return attributes.descriptions
359359
}
360360

361361
var isFinal: Bool {
@@ -412,6 +412,15 @@ fileprivate func findNamespaces(parent: Syntax?) -> [String] {
412412
}
413413

414414
extension AttributeListSyntax {
415+
fileprivate var descriptions: [String] {
416+
return compactMap { element in
417+
guard case .attribute(let attribute) = element else {
418+
return nil
419+
}
420+
return attribute.trimmedDescription
421+
}
422+
}
423+
415424
fileprivate var mayHaveGlobalActor: Bool {
416425
let wellKnownGlobalActor: Set<String> = [.mainActor]
417426
return self.contains { element in

Tests/TestActor/FixtureActor.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@
100100
init() { }
101101
}
102102

103+
@available(iOS 18.0, *)
103104
class P1Mock: P1 {
104105
init() { }
105106
}

Tests/TestSendable/FixtureSendable.swift

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,4 +208,76 @@
208208
}
209209
}
210210
}
211+
212+
@Fixture enum availableSendableProtocol {
213+
@available(macOS 99.0, *)
214+
struct Bar {}
215+
216+
/// @mockable
217+
@available(macOS 99.0, *)
218+
protocol Foo: Sendable {
219+
func bar() -> Bar
220+
}
221+
222+
@Fixture(includesConcurrencyHelpers: true)
223+
enum expected {
224+
@available(macOS 99.0, *)
225+
final class FooMock: Foo, @unchecked Sendable {
226+
init() { }
227+
228+
229+
private let barState = MockoloMutex(MockoloHandlerState<Never, @Sendable () -> Bar>())
230+
var barCallCount: Int {
231+
return barState.withLock(\.callCount)
232+
}
233+
var barHandler: (@Sendable () -> Bar)? {
234+
get { barState.withLock(\.handler) }
235+
set { barState.withLock { $0.handler = newValue } }
236+
}
237+
func bar() -> Bar {
238+
let barHandler = barState.withLock { state in
239+
state.callCount += 1
240+
return state.handler
241+
}
242+
if let barHandler = barHandler {
243+
return barHandler()
244+
}
245+
fatalError("barHandler returns can't have a default value thus its handler must be set")
246+
}
247+
}
248+
}
249+
}
250+
251+
@Fixture enum availableInheritedProtocol {
252+
@available(macOS 100.0, *)
253+
struct Bar {}
254+
255+
@available(macOS 90.0, *)
256+
protocol Foo {
257+
}
258+
259+
/// @mockable
260+
@available(macOS 100.0, *)
261+
protocol Foo2: Foo {
262+
var bar: Bar { get set }
263+
}
264+
265+
@Fixture enum expected {
266+
@available(macOS 100.0, *)
267+
class Foo2Mock: Foo2 {
268+
init() { }
269+
init(bar: Bar) {
270+
self._bar = bar
271+
}
272+
273+
274+
private(set) var barSetCallCount = 0
275+
private var _bar: Bar! { didSet { barSetCallCount += 1 } }
276+
var bar: Bar {
277+
get { return _bar }
278+
set { _bar = newValue }
279+
}
280+
}
281+
}
282+
}
211283
#endif

Tests/TestSendable/SendableTests.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,15 @@ class SendableTests: MockoloTestCase {
2121
verify(srcContent: confirmedSendableProtocol._source,
2222
dstContent: confirmedSendableProtocol.expected._source)
2323
}
24+
25+
func testAvailableSendableProtocol() {
26+
verify(srcContent: availableSendableProtocol._source,
27+
dstContent: availableSendableProtocol.expected._source)
28+
}
29+
30+
func testAvailableInheritedProtocol() {
31+
verify(srcContent: availableInheritedProtocol._source,
32+
dstContent: availableInheritedProtocol.expected._source)
33+
}
2434
}
2535
#endif

0 commit comments

Comments
 (0)