diff --git a/Sources/MockingbirdGenerator/Generator/Templates/SubscriptMethodTemplate.swift b/Sources/MockingbirdGenerator/Generator/Templates/SubscriptMethodTemplate.swift index 77057fc1..4dce6775 100644 --- a/Sources/MockingbirdGenerator/Generator/Templates/SubscriptMethodTemplate.swift +++ b/Sources/MockingbirdGenerator/Generator/Templates/SubscriptMethodTemplate.swift @@ -76,12 +76,16 @@ class SubscriptMethodTemplate: MethodTemplate { }, invocationArguments: setterInvocationArguments).render()) + let accessors = method.attributes.contains(.readonly) ? [getterDefinition.render()] : [ + getterDefinition.render(), + setterDefinition.render(), + ] + return String(lines: [ "// MARK: Mocked \(fullNameForMocking)", VariableDefinitionTemplate(attributes: method.attributes.safeDeclarations, declaration: "public \(overridableModifiers)\(uniqueDeclaration)", - body: String(lines: [getterDefinition.render(), - setterDefinition.render()])).render(), + body: String(lines: accessors)).render(), ]) } diff --git a/Sources/MockingbirdGenerator/Parser/Models/Method.swift b/Sources/MockingbirdGenerator/Parser/Models/Method.swift index f831330f..b655c28a 100644 --- a/Sources/MockingbirdGenerator/Parser/Models/Method.swift +++ b/Sources/MockingbirdGenerator/Parser/Models/Method.swift @@ -62,6 +62,8 @@ struct Method { rawParametersDeclaration) = Method.parseDeclaration(from: dictionary, source: source, isInitializer: isInitializer, + kind: kind, + rootKind: rootKind, attributes: attributes) // Parse return type. @@ -121,6 +123,8 @@ struct Method { private static func parseDeclaration(from dictionary: StructureDictionary, source: Data?, isInitializer: Bool, + kind: SwiftDeclarationKind, + rootKind: SwiftDeclarationKind, attributes: Attributes) -> (Attributes, Substring?) { guard let declaration = SourceSubstring.key.extract(from: dictionary, contents: source) else { return (attributes, nil) } @@ -159,9 +163,34 @@ struct Method { fullAttributes.insert(.throws) } + // Parse kind-specific attributes. + switch kind { + case .functionSubscript: + if isReadOnlySubscript(from: dictionary, rootKind: rootKind, source: source) { + fullAttributes.insert(.readonly) + } + default: + break + } + return (fullAttributes, rawParametersDeclaration) } + private static func isReadOnlySubscript(from dictionary: StructureDictionary, + rootKind: SwiftDeclarationKind, + source: Data?) -> Bool { + let setterAccessLevel = AccessLevel(setter: dictionary) + if rootKind == .class { + guard setterAccessLevel != .fileprivate, + setterAccessLevel != .private + else { return true } + let body = SourceSubstring.body.extract(from: dictionary, contents: source) ?? "" + return !body.contains("set", excluding: ["{": "}"]) + } else { + return setterAccessLevel == nil + } + } + private static func parseArgumentLabels(name: String, parameters: Substring?) -> (shortName: String, labels: [String?]) { let (shortName, labels) = name.extractArgumentLabels() diff --git a/Sources/MockingbirdTestsHost/Subscripts.swift b/Sources/MockingbirdTestsHost/Subscripts.swift index 62f4fe78..df1ff719 100644 --- a/Sources/MockingbirdTestsHost/Subscripts.swift +++ b/Sources/MockingbirdTestsHost/Subscripts.swift @@ -4,7 +4,7 @@ protocol SubscriptedProtocol { subscript(index: Int) -> String { get set } subscript(index: Int) -> Bool { get set } // Overloaded parameter type subscript(index: String) -> String { get set } // Overloaded return type - subscript(index: Int) -> Int { get } // Only getter + subscript(object: AnyObject) -> Int { get } // Only getter subscript(row: Int, column: Int) -> String { get set } // Multiple parameters subscript(indexes: String...) -> String { get set } // Variadic parameter subscript(index: IndexType) -> ReturnType { get set } @@ -29,10 +29,16 @@ class SubscriptedClass { } // Only getter - subscript(index: Int) -> Int { + subscript(object: AnyObject) -> Int { get { fatalError() } } + // Private setter + private(set) subscript(object: NSObject) -> Int { + get { fatalError() } + set { fatalError() } + } + // Multiple parameters subscript(row: Int, column: Int) -> String { get { fatalError() } diff --git a/Tests/MockingbirdTests/Framework/SubscriptTests.swift b/Tests/MockingbirdTests/Framework/SubscriptTests.swift index 0f4245a7..efb1892d 100644 --- a/Tests/MockingbirdTests/Framework/SubscriptTests.swift +++ b/Tests/MockingbirdTests/Framework/SubscriptTests.swift @@ -15,25 +15,28 @@ class SubscriptTests: BaseTestCase { classMock = mock(SubscriptedClass.self) } + private class MyObject {} + // MARK: - Protocol mock // MARK: Getter func testSubscriptProtocol_handlesBasicSingleParameterGetter() { + let object = MyObject() given(protocolMock.getSubscript(42)) ~> "bar" given(protocolMock.getSubscript(42)) ~> true given(protocolMock.getSubscript("foo")) ~> "bar" - given(protocolMock.getSubscript(42)) ~> 99 + given(protocolMock.getSubscript(object)) ~> 99 XCTAssertEqual(protocolInstance[42], "bar") XCTAssertEqual(protocolInstance[42], true) XCTAssertEqual(protocolInstance["foo"], "bar") - XCTAssertEqual(protocolInstance[42], 99) + XCTAssertEqual(protocolInstance[object], 99) verify(protocolMock.getSubscript(42)).returning(String.self).wasCalled() verify(protocolMock.getSubscript(42)).returning(Bool.self).wasCalled() verify(protocolMock.getSubscript("foo")).returning(String.self).wasCalled() - verify(protocolMock.getSubscript(42)).returning(Int.self).wasCalled() + verify(protocolMock.getSubscript(object)).returning(Int.self).wasCalled() } func testSubscriptProtocol_handlesMultipleParameterGetter() { @@ -109,20 +112,21 @@ class SubscriptTests: BaseTestCase { // MARK: - Class mock func testSubscriptClass_handlesBasicSingleParameterCalls() { + let object = MyObject() given(classMock.getSubscript(42)) ~> "bar" given(classMock.getSubscript(42)) ~> true given(classMock.getSubscript("foo")) ~> "bar" - given(classMock.getSubscript(42)) ~> 99 + given(classMock.getSubscript(object)) ~> 99 XCTAssertEqual(classInstance[42], "bar") XCTAssertEqual(classInstance[42], true) XCTAssertEqual(classInstance["foo"], "bar") - XCTAssertEqual(classInstance[42], 99) + XCTAssertEqual(classInstance[object], 99) verify(classMock.getSubscript(42)).returning(String.self).wasCalled() verify(classMock.getSubscript(42)).returning(Bool.self).wasCalled() verify(classMock.getSubscript("foo")).returning(String.self).wasCalled() - verify(classMock.getSubscript(42)).returning(Int.self).wasCalled() + verify(classMock.getSubscript(object)).returning(Int.self).wasCalled() } func testSubscriptClass_handlesMultipleParameterCalls() {