Skip to content

Commit

Permalink
Support method overriding in AutoMockable (#1240)
Browse files Browse the repository at this point in the history
* updated automockable stencil template

* updated expected AutoMockable

* updated CHANGELOG

* added supported case for vararg

* Added example protocols for overrides

* More flexible support for overridden return types

* Added support for closure return type

* Added more methods for tests

* updated AutoMockable.expected

* reverted wrong trailing whitespace trimming

* added missing member lookup

* Squashed commit of the following:

commit 7f85e02
Author: Ruslan Alikhamov <r.alikhamov@gmail.com>
Date:   Wed Dec 20 01:10:31 2023 +0400

    Support for variadic types as method arguments (#1241)

    * Added support for variadic types as method arguments

    * Added changelog entry

    * added example protocol with varargs

    * support for vararg in return type's closure

    * added missing member lookup

* Squashed commit of the following:

commit 7f85e02
Author: Ruslan Alikhamov <r.alikhamov@gmail.com>
Date:   Wed Dec 20 01:10:31 2023 +0400

    Support for variadic types as method arguments (#1241)

    * Added support for variadic types as method arguments

    * Added changelog entry

    * added example protocol with varargs

    * support for vararg in return type's closure

    * added missing member lookup

* Squashed commit of the following:

commit 7f85e02
Author: Ruslan Alikhamov <r.alikhamov@gmail.com>
Date:   Wed Dec 20 01:10:31 2023 +0400

    Support for variadic types as method arguments (#1241)

    * Added support for variadic types as method arguments

    * Added changelog entry

    * added example protocol with varargs

    * support for vararg in return type's closure

    * added missing member lookup

* Revert "Merge branch 'master' into support-method-overriding"

This reverts commit 3c81133, reversing
changes made to 48816e2.

* Revert "Squashed commit of the following:"

This reverts commit 4f7d246.

# Conflicts:
#	Templates/Tests/Expected/AutoMockable.expected

* Updated AutoMockable.expected
  • Loading branch information
art-divin committed Dec 20, 2023
1 parent 7f85e02 commit 07a8b28
Show file tree
Hide file tree
Showing 7 changed files with 767 additions and 552 deletions.
7 changes: 4 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
# Sourcery CHANGELOG
## 2.1.3
## Changes
- Add support for `typealias`es in EJS templates. ([#1208](https://github.com/krzysztofzablocki/Sourcery/pull/1208)
- Add support for existential to Automockable Protocol with generic types. ([#1220](https://github.com/krzysztofzablocki/Sourcery/pull/1220)
- Add support for `typealias`es in EJS templates. ([#1208](https://github.com/krzysztofzablocki/Sourcery/pull/1208))
- Add support for existential to Automockable Protocol with generic types. ([#1220](https://github.com/krzysztofzablocki/Sourcery/pull/1220))
- Throw throwable error after updating mocks's calls counts and received parameters/invocations.
([#1224](https://github.com/krzysztofzablocki/Sourcery/pull/1224)
([#1224](https://github.com/krzysztofzablocki/Sourcery/pull/1224))
- Fix unit tests on Linux ([#1225](https://github.com/krzysztofzablocki/Sourcery/pull/1225))
- Updated XcodeProj to 8.16.0 ([#1228](https://github.com/krzysztofzablocki/Sourcery/pull/1228))
- Fixed Unable to mock a protocol with methods that differ in parameter type - Error: "Invalid redeclaration" ([#1238](https://github.com/krzysztofzablocki/Sourcery/issues/1238))
- Support for variadic arguments as method parameters ([#1222](https://github.com/krzysztofzablocki/Sourcery/issues/1222))

## 2.1.2
Expand Down
18 changes: 18 additions & 0 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,24 @@
"version" : "0.9.1"
}
},
{
"identity" : "cwlcatchexception",
"kind" : "remoteSourceControl",
"location" : "https://github.com/mattgallagher/CwlCatchException.git",
"state" : {
"revision" : "f809deb30dc5c9d9b78c872e553261a61177721a",
"version" : "2.0.0"
}
},
{
"identity" : "cwlpreconditiontesting",
"kind" : "remoteSourceControl",
"location" : "https://github.com/mattgallagher/CwlPreconditionTesting.git",
"state" : {
"revision" : "02b7a39a99c4da27abe03cab2053a9034379639f",
"version" : "2.0.0"
}
},
{
"identity" : "nimble",
"kind" : "remoteSourceControl",
Expand Down
2 changes: 2 additions & 0 deletions SourceryRuntime/Sources/AST/Method_Linux.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ public final class Method: NSObject, SourceryModel, Annotated, Documented, Defin
return attributes
case "isOptionalReturnType":
return isOptionalReturnType
case "actualReturnTypeName":
return actualReturnTypeName
default:
fatalError("unable to lookup: \(member) in \(self)")
}
Expand Down
51 changes: 27 additions & 24 deletions Templates/Templates/AutoMockable.stencil
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,19 @@ import {{ import }}
@testable import {{ import }}
{% endfor %}

{% macro swiftifyMethodName name %}{{ name | replace:"(","_" | replace:")","" | replace:":","_" | replace:"`","" | snakeToCamelCase | lowerFirstWord }}{% endmacro %}
{% macro cleanString string %}{{ string | replace:"(","_" | replace:")","" | replace:":","_" | replace:"`","" | replace:" ","_" | replace:"?","_" | replace:"!","_" | replace:",","_" | replace:"->","_" | replace:"@","_" | replace:".","_" | replace:"[","" | replace:"]","" | snakeToCamelCase }}{% endmacro %}
{% macro swiftifyMethodName method %}{% call cleanString method.name | lowerFirstWord %}{% call cleanString method.actualReturnTypeName.name | upperFirstLetter %}{% endmacro %}

{% macro accessLevel level %}{% if level != 'internal' %}{{ level }} {% endif %}{% endmacro %}

{% macro staticSpecifier method %}{% if method.isStatic and not method.isInitializer %}static {% endif %}{% endmacro %}

{% macro methodThrowableErrorDeclaration method %}
{% call accessLevel method.accessLevel %}{% call staticSpecifier method %}var {% call swiftifyMethodName method.selectorName %}ThrowableError: Error?
{% call accessLevel method.accessLevel %}{% call staticSpecifier method %}var {% call swiftifyMethodName method %}ThrowableError: Error?
{% endmacro %}

{% macro methodThrowableErrorUsage method %}
if let error = {% call swiftifyMethodName method.selectorName %}ThrowableError {
if let error = {% call swiftifyMethodName method %}ThrowableError {
throw error
}
{% endmacro %}
Expand All @@ -39,17 +40,17 @@ import {{ import }}
{% endfor -%}
{% endset %}
{% if method.parameters.count == 1 and not hasNonEscapingClosures %}
{% call swiftifyMethodName method.selectorName %}Received{% for param in method.parameters %}{{ param.name|upperFirstLetter }} = {{ param.name }}{% endfor %}
{% call swiftifyMethodName method.selectorName %}ReceivedInvocations.append({% for param in method.parameters %}{{ param.name }}){% endfor %}
{% call swiftifyMethodName method %}Received{% for param in method.parameters %}{{ param.name|upperFirstLetter }} = {{ param.name }}{% endfor %}
{% call swiftifyMethodName method %}ReceivedInvocations.append({% for param in method.parameters %}{{ param.name }}){% endfor %}
{% else %}
{% if not method.parameters.count == 0 and not hasNonEscapingClosures %}
{% call swiftifyMethodName method.selectorName %}ReceivedArguments = ({% for param in method.parameters %}{{ param.name }}: {{ param.name }}{% if not forloop.last%}, {% endif %}{% endfor %})
{% call swiftifyMethodName method.selectorName %}ReceivedInvocations.append(({% for param in method.parameters %}{{ param.name }}: {{ param.name }}{% if not forloop.last%}, {% endif %}{% endfor %}))
{% call swiftifyMethodName method %}ReceivedArguments = ({% for param in method.parameters %}{{ param.name }}: {{ param.name }}{% if not forloop.last%}, {% endif %}{% endfor %})
{% call swiftifyMethodName method %}ReceivedInvocations.append(({% for param in method.parameters %}{{ param.name }}: {{ param.name }}{% if not forloop.last%}, {% endif %}{% endfor %}))
{% endif %}
{% endif %}
{% endmacro %}

{% macro methodClosureName method %}{% call swiftifyMethodName method.selectorName %}Closure{% endmacro %}
{% macro methodClosureName method %}{% call swiftifyMethodName method %}Closure{% endmacro %}

{% macro closureReturnTypeName method %}{% if method.isOptionalReturnType %}{{ method.unwrappedReturnTypeName }}?{% else %}{{ method.returnTypeName }}{% endif %}{% endmacro %}

Expand All @@ -66,9 +67,9 @@ import {{ import }}
{% call methodThrowableErrorDeclaration method %}
{% endif %}
{% if not method.isInitializer %}
{% call accessLevel method.accessLevel %}{% call staticSpecifier method %}var {% call swiftifyMethodName method.selectorName %}CallsCount = 0
{% call accessLevel method.accessLevel %}{% call staticSpecifier method %}var {% call swiftifyMethodName method.selectorName %}Called: Bool {
return {% call swiftifyMethodName method.selectorName %}CallsCount > 0
{% call accessLevel method.accessLevel %}{% call staticSpecifier method %}var {% call swiftifyMethodName method %}CallsCount = 0
{% call accessLevel method.accessLevel %}{% call staticSpecifier method %}var {% call swiftifyMethodName method %}Called: Bool {
return {% call swiftifyMethodName method %}CallsCount > 0
}
{% endif %}
{% set hasNonEscapingClosures %}
Expand All @@ -77,14 +78,14 @@ import {{ import }}
{% endfor -%}
{% endset %}
{% if method.parameters.count == 1 and not hasNonEscapingClosures %}
{% call accessLevel method.accessLevel %}{% call staticSpecifier method %}var {% call swiftifyMethodName method.selectorName %}Received{% for param in method.parameters %}{{ param.name|upperFirstLetter }}: {{ '(' if param.isClosure }}({% call existentialClosureVariableTypeName param.typeName.unwrappedTypeName param.isVariadic %}{{ ')' if param.isClosure }})?{% endfor %}
{% call accessLevel method.accessLevel %}{% call staticSpecifier method %}var {% call swiftifyMethodName method.selectorName %}ReceivedInvocations{% for param in method.parameters %}: [{{ '(' if param.isClosure }}({% call existentialClosureVariableTypeName param.typeName.unwrappedTypeName param.isVariadic %}){{ ')' if param.isClosure }}{%if param.typeName.isOptional%}?{%endif%}]{% endfor %} = []
{% call accessLevel method.accessLevel %}{% call staticSpecifier method %}var {% call swiftifyMethodName method %}Received{% for param in method.parameters %}{{ param.name|upperFirstLetter }}: {{ '(' if param.isClosure }}({% call existentialClosureVariableTypeName param.typeName.unwrappedTypeName param.isVariadic %}{{ ')' if param.isClosure }})?{% endfor %}
{% call accessLevel method.accessLevel %}{% call staticSpecifier method %}var {% call swiftifyMethodName method %}ReceivedInvocations{% for param in method.parameters %}: [{{ '(' if param.isClosure }}({% call existentialClosureVariableTypeName param.typeName.unwrappedTypeName param.isVariadic %}){{ ')' if param.isClosure }}{%if param.typeName.isOptional%}?{%endif%}]{% endfor %} = []
{% elif not method.parameters.count == 0 and not hasNonEscapingClosures %}
{% call accessLevel method.accessLevel %}{% call staticSpecifier method %}var {% call swiftifyMethodName method.selectorName %}ReceivedArguments: ({% for param in method.parameters %}{{ param.name }}: {% if param.typeAttributes.escaping %}{% call existentialClosureVariableTypeName param.typeName.unwrappedTypeName param.isVariadic %}{% else %}{% call existentialClosureVariableTypeName param.typeName param.isVariadic %}{% endif %}{{ ', ' if not forloop.last }}{% endfor %})?
{% call accessLevel method.accessLevel %}{% call staticSpecifier method %}var {% call swiftifyMethodName method.selectorName %}ReceivedInvocations: [({% for param in method.parameters %}{{ param.name }}: {% if param.typeAttributes.escaping %}{% call existentialClosureVariableTypeName param.typeName.unwrappedTypeName param.isVariadic %}{% else %}{% call existentialClosureVariableTypeName param.typeName param.isVariadic %}{% endif %}{{ ', ' if not forloop.last }}{% endfor %})] = []
{% call accessLevel method.accessLevel %}{% call staticSpecifier method %}var {% call swiftifyMethodName method %}ReceivedArguments: ({% for param in method.parameters %}{{ param.name }}: {% if param.typeAttributes.escaping %}{% call existentialClosureVariableTypeName param.typeName.unwrappedTypeName param.isVariadic %}{% else %}{% call existentialClosureVariableTypeName param.typeName param.isVariadic %}{% endif %}{{ ', ' if not forloop.last }}{% endfor %})?
{% call accessLevel method.accessLevel %}{% call staticSpecifier method %}var {% call swiftifyMethodName method %}ReceivedInvocations: [({% for param in method.parameters %}{{ param.name }}: {% if param.typeAttributes.escaping %}{% call existentialClosureVariableTypeName param.typeName.unwrappedTypeName param.isVariadic %}{% else %}{% call existentialClosureVariableTypeName param.typeName param.isVariadic %}{% endif %}{{ ', ' if not forloop.last }}{% endfor %})] = []
{% endif %}
{% if not method.returnTypeName.isVoid and not method.isInitializer %}
{% call accessLevel method.accessLevel %}{% call staticSpecifier method %}var {% call swiftifyMethodName method.selectorName %}ReturnValue: {{ '(' if method.returnTypeName.isClosure and not method.isOptionalReturnType or method.returnTypeName|contains:"any "}}{% call existentialVariableTypeName method.returnTypeName %}{{ ')' if method.returnTypeName.isClosure and not method.isOptionalReturnType or method.returnTypeName|contains:"any " }}{{ '!' if not method.isOptionalReturnType }}
{% call accessLevel method.accessLevel %}{% call staticSpecifier method %}var {% call swiftifyMethodName method %}ReturnValue: {{ '(' if method.returnTypeName.isClosure and not method.isOptionalReturnType or method.returnTypeName|contains:"any "}}{% call existentialVariableTypeName method.returnTypeName %}{{ ')' if method.returnTypeName.isClosure and not method.isOptionalReturnType or method.returnTypeName|contains:"any " }}{{ '!' if not method.isOptionalReturnType }}
{% endif %}
{% call methodClosureDeclaration method %}

Expand All @@ -100,7 +101,7 @@ import {{ import }}
{% endfor %}
{% endfor %}
{% call accessLevel method.accessLevel %}{% call staticSpecifier method %}{% call methodName method %}{{ ' async' if method.isAsync }}{{ ' throws' if method.throws }}{% if not method.returnTypeName.isVoid %} -> {% call existentialVariableTypeName method.returnTypeName %}{% endif %} {
{% call swiftifyMethodName method.selectorName %}CallsCount += 1
{% call swiftifyMethodName method %}CallsCount += 1
{% call methodReceivedParameters method %}
{% if method.throws %}
{% call methodThrowableErrorUsage method %}
Expand All @@ -111,7 +112,7 @@ import {{ import }}
if let {% call methodClosureName method %} = {% call methodClosureName method %} {
return {{ 'try ' if method.throws }}{{ 'await ' if method.isAsync }}{% call methodClosureName method %}({% call methodClosureCallParameters method %})
} else {
return {% call swiftifyMethodName method.selectorName %}ReturnValue
return {% call swiftifyMethodName method %}ReturnValue
}
{% endif %}
}
Expand All @@ -123,18 +124,18 @@ import {{ import }}
{# for type method which are mocked, a way to reset the invocation, argument, etc #}
{% if method.isStatic and not method.isInitializer %} //MARK: - {{ method.shortName }}
{% if not method.isInitializer %}
{% call swiftifyMethodName method.selectorName %}CallsCount = 0
{% call swiftifyMethodName method %}CallsCount = 0
{% endif %}
{% if method.parameters.count == 1 %}
{% call swiftifyMethodName method.selectorName %}Received{% for param in method.parameters %}{{ param.name|upperFirstLetter }}{% endfor %} = nil
{% call swiftifyMethodName method.selectorName %}ReceivedInvocations = []
{% call swiftifyMethodName method %}Received{% for param in method.parameters %}{{ param.name|upperFirstLetter }}{% endfor %} = nil
{% call swiftifyMethodName method %}ReceivedInvocations = []
{% elif not method.parameters.count == 0 %}
{% call swiftifyMethodName method.selectorName %}ReceivedArguments = nil
{% call swiftifyMethodName method.selectorName %}ReceivedInvocations = []
{% call swiftifyMethodName method %}ReceivedArguments = nil
{% call swiftifyMethodName method %}ReceivedInvocations = []
{% endif %}
{% call methodClosureName method %} = nil
{% if method.throws %}
{% call swiftifyMethodName method.selectorName %}ThrowableError = nil
{% call swiftifyMethodName method %}ThrowableError = nil
{% endif %}

{% endif %}
Expand Down Expand Up @@ -217,6 +218,8 @@ import {{ import }}
{{ typeName | replace:"some","(some" | replace:"?",")?" }}
{%- elif typeName|contains:"some " and typeName.isClosure and typeName|contains:"?" -%}
({{ typeName | replace:"some","(some" | replace:"?",")?" }})
{%- elif typeName.isClosure -%}
({{ typeName }})
{%- else -%}
{{ typeName }}
{%- endif -%}
Expand Down
11 changes: 11 additions & 0 deletions Templates/Tests/Context/AutoMockable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -215,3 +215,14 @@ protocol HouseProtocol: AutoMockable {
protocol ExampleVararg {
func string(key: String, args: CVarArg...) -> String
}

// sourcery: AutoMockable
public protocol ProtocolWithOverrides {
func doSomething(_ data: Int) -> [String]
func doSomething(_ data: String) -> [String]
func doSomething(_ data: String) -> [Int]
func doSomething(_ data: String) -> ([Int], [String])
func doSomething(_ data: String) throws -> ([Int], [Any])
func doSomething(_ data: String) -> (([Int], [String]) -> Void)
func doSomething(_ data: String) throws -> (([Int], [Any]) -> Void)
}
11 changes: 11 additions & 0 deletions Templates/Tests/Context_Linux/AutoMockable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -215,3 +215,14 @@ protocol HouseProtocol: AutoMockable {
protocol ExampleVararg {
func string(key: String, args: CVarArg...) -> String
}

// sourcery: AutoMockable
public protocol ProtocolWithOverrides {
func doSomething(_ data: Int) -> [String]
func doSomething(_ data: String) -> [String]
func doSomething(_ data: String) -> [Int]
func doSomething(_ data: String) -> ([Int], [String])
func doSomething(_ data: String) throws -> ([Int], [Any])
func doSomething(_ data: String) -> (([Int], [String]) -> Void)
func doSomething(_ data: String) throws -> (([Int], [Any]) -> Void)
}

0 comments on commit 07a8b28

Please sign in to comment.