Sourcery scans your source code, applies your personal templates and generates Swift code for you, allowing you to use meta-programming techniques to save time and decrease potential mistakes.
Using it offers many benefits:
- Write less repetitive code and make it easy to adhere to DRY principle.
- It allows you to create better code, one that would be hard to maintain without it, e.g. performing automatic property level difference in tests
- Limits the risk of introducing human error when refactoring.
- Sourcery doesn't use runtime tricks, in fact, it allows you to leverage compiler, even more, creating more safety.
- Immediate feedback: Sourcery features built-in daemon support, enabling you to write your templates in real-time side-by-side with generated code.
Sourcery is so meta that it is used to code-generate its boilerplate code
Table of Contents generated with DocToc
- What is Sourcery?
- Why?
- Examples
- Writing templates
- Installing
- Usage
- Contributing
- License
- Attributions
- Other Libraries / Tools
Swift features very limited runtime and no meta-programming features. Which leads our projects to contain boilerplate code.
Sourcery exists to allow Swift developers to stop doing the same thing over and over again while still maintaining strong typing, preventing bugs and leveraging compiler.
Have you ever?
- Had to write equatable/hashable?
- Had to write NSCoding support?
- Had to implement JSON serialization?
- Wanted to use Lenses?
If you did then you probably found yourself writing repetitive code to deal with those scenarios, does this feel right?
Even worse, if you ever add a new property to a type all of those implementations have to be updated, or you will end up with bugs. In those scenarios usually compiler will not generate the error for you, which leads to error prone code.
I want to generate `Equatable` implementation
Template used to generate equality for all types that conform to :AutoEquatable
, allowing us to avoid writing boilerplate code.
It adds :Equatable
conformance to all types, except protocols (because it would require turning them into PAT's).
For protocols it's just generating func ==
.
skipEquality
allows you to skip variable from being compared.arrayEquality
mark this to use array comparsion for variables that have array of items that don't implementEquatable
but have==
operator e.g. Protocols
// MARK: - AdNodeViewModel AutoEquatable
extension AdNodeViewModel: Equatable {}
internal func == (lhs: AdNodeViewModel, rhs: AdNodeViewModel) -> Bool {
guard lhs.remoteAdView == rhs.remoteAdView else { return false }
guard lhs.hidesDisclaimer == rhs.hidesDisclaimer else { return false }
guard lhs.type == rhs.type else { return false }
guard lhs.height == rhs.height else { return false }
guard lhs.attributedDisclaimer == rhs.attributedDisclaimer else { return false }
return true
}
I want to generate `Hashable` implementation
Template used to generate hashing for all types that conform to :AutoHashable
, allowing us to avoid writing boilerplate code.
It adds :Hashable
conformance to all types, except protocols (because it would require turning them into PAT's).
For protocols it's just generating var hashValue
comparator.
skipHashing
allows you to skip variable from being compared.includeInHashing
is only applied on enums and allows us to add some computed variable into hashing logic
// MARK: - AdNodeViewModel AutoHashable
extension AdNodeViewModel: Hashable {
internal var hashValue: Int {
return combineHashes(remoteAdView.hashValue, hidesDisclaimer.hashValue, type.hashValue, height.hashValue, attributedDisclaimer.hashValue, 0)
}
}
I want to list all cases in an enum
Generate count
and allCases
for any enumeration that is marked with AutoCases
phantom protocol.
extension BetaSettingsGroup {
static var count: Int { return 8 }
static var allCases: [BetaSettingsGroup] {
return [
.featuresInDevelopment,
.advertising,
.analytics,
.marketing,
.news,
.notifications,
.tech,
.appInformation
]
}
}
I want to generate test mocks for protocols
Contributed by @marinbenc
Create a class called ProtocolNameMock
in which it will...
For each function:
- Implement the function
- Add a
functionCalled
boolean to check if the function was called - Add a
functionRecievedArguments
tuple to check the arguments that were passed to the function - Add a
functionReturnValue
variable and return it when the function is called.
For each variable:
- Add a gettable and settable variable with the same name and type
- Overloaded methods will produce compiler erros since the variables above the functions have the same name. Workaround: delete the variables on top of one of the functions, or rename them.
- Handling success/failure cases (for callbacks) is tricky to do automatically, so you have to do that yourself.
- This is not a full replacement for hand-written mocks, but it will get you 90% of the way there. Any more complex logic than changing return types, you will have to implement yourself. This only removes the most boring boilerplate you have to write.
class MockableServiceMock: MockableService {
//MARK: - functionWithArguments
var functionWithArgumentsCalled = false
var functionWithArgumentsRecievedArguments: (firstArgument: String, onComplete: (String)-> Void)?
//MARK: - functionWithCallback
var functionWithCallbackCalled = false
var functionWithCallbackRecievedArguments: (firstArgument: String, onComplete: (String)-> Void)?
func functionWithCallback(_ firstArgument: String, onComplete: @escaping (String)-> Void) {
functionWithCallbackCalled = true
functionWithCallbackRecievedArguments = (firstArgument: firstArgument, onComplete: onComplete)
}
...
I want to generate Lenses for all structs
Contributed by @filip_zawada
What are Lenses? Great explanation by @mbrandonw
This script assumes you follow swift naming convention, e.g. structs start with an upper letter.
extension House {
static let roomsLens = Lens<House, Room>(
get: { $0.rooms },
set: { rooms, house in
House(rooms: rooms, address: house.address, size: house.size)
}
)
static let addressLens = Lens<House, String>(
get: { $0.address },
set: { address, house in
House(rooms: house.rooms, address: address, size: house.size)
}
)
...
I want to have diffing in tests
Template used to generate much better output when using equality in tests, instead of having to read wall of text it's used to generate precise property level differences. This template uses Sourcery Diffable implementation
skipEquality
allows you to skip variable from being compared.
I want to generate `LinuxMain.swift` for all my tests
For all test cases generates allTests
static variable and passes all of them as XCTestCaseEntry
to XCTMain
. Run with --args testimports='import MyTests'
parameter to import test modules.
disableTests
allows you to disable the whole test case.
import XCTest
//testimports
extension AutoInjectionTests {
static var allTests = [
("testThatItResolvesAutoInjectedDependencies", testThatItResolvesAutoInjectedDependencies),
...
]
}
extension AutoWiringTests {
static var allTests = [
("testThatItCanResolveWithAutoWiring", testThatItCanResolveWithAutoWiring),
...
]
}
...
XCTMain([
testCase(AutoInjectionTests.allTests),
testCase(AutoWiringTests.allTests),
...
])
Sourcery templates are powered by Stencil
Make sure you leverage Sourcery built-in daemon to make writing templates a pleasure: you can open template side-by-side with generated code and see it change live.
There are multiple ways to access your types:
type.TypeName
=> access specific type by nametypes.all
=> all types, excluding protocolstypes.classes
types.structs
types.enums
types.protocols
=> lists all protocols (that were defined in the project)types.inheriting.BaseClass
=> lists all types inherting from known BaseClass (only those that were defined in source code that Sourcery scanned)types.implementing.Protocol
=> lists all types conforming to given Protocol (only those that were defined in source code that Sourcery scanned)types.based.BaseClassOrProtocol
=> lists all types implementing or inheriting fromBaseClassOrProtocol
(all type names encountered, even those that Sourcery didn't scan)
All of these properties return Type
objects.
**What are _known_ and _unknown_ types**
Currently Sourcery only scans files from a directory that you tell it to scan. This way it can get full information about types defined in these sources. These types are considered known types. For each of known types Sourcery provides Type
object. You can get it for example by its name from types
collection. Type
object contains information about whether type that it describes is a struct, enum, class or a protocol, what are its properties and methods, what protocols it implements and so on. This is done recursively, so if you have a class that inherits from another class (or struct that implements a protocol) and they are both known types you will have information about both of them and you will be able to access parent type's Type
object using type.inherits.TypeName
(or type.implements.ProtocolName
).
Everything defined outside of scanned sources is considered as unknown types. For such types Sourcery doesn't provide Type
object. For that reason variables (and other "typed" types, like method parameters etc.) of such types will only contain typeName
property, but their type
property will be nil
.
If you have an extension of unknown type defined in scanned sources Sourcery will create Type
for it (it's kind
property will be extension
). But this object will contain only declarations defined in this extension. Several extensions of unknown type will be merged into one Type
object the same way as extensions of known types.
See #87 for details.
Available types:
**Type**. Properties:
name
<- namekind
<- convience accessor that will contain one ofenum
,class
,struct
,protocol
, it will also provideextension
for types that are unknown to us(e.g. 3rd party or objc), but had extension in the projectisGeneric
<- info whether the type is genericlocalName
<- name within parent scopevariables
<- list of all variables defined in this type, excluding variables from protocols or inheritance- if you want to access all available variables, including those from inherited / protocol, then use
allVariables
- if you want to accces computed, stored, instance, or static variables, you can do so using our custom filters on both
variables
andallVariables
- if you want to access all available variables, including those from inherited / protocol, then use
methods
<- list of all methods defined in this type, excluding those from protocols or inheritanceallMethods
<- same principles as inallVariables
initializers
<- list of all initializersinherits.BaseClass
=> if type is a class and it inherits from a known class namedBaseClass
this property returnsType
object forBaseClass
, otherwise returnsnil
implements.Protocol
=> if type implements a known protocol namedProtocol
this property returnsType
object forProtocol
, otherwise returnsnil
based.BaseClassOrProtocol
=> if type either implements a protocol or inherits from a class namedBaseClassOrProtocol
this property returnsBaseClassOrProtocol
itself, otherwise returnsnil
. All type names encountered, even those that Sourcery didn't scancontainedTypes
<- list of types contained within this typeparentName
<- list of parent type (for contained ones)attributes
<- type attributes, i.e.type.attributes.objc
annotations
<- dictionary with configured annotations
**Enum**. Built on top of `Type` and provides some additional properties:
rawType
<- enum raw typecases
<- list ofEnum.Case
hasAssociatedValues
<- true if any of cases has associated values
**EnumCase**. Properties:
name
<- namerawValue
<- raw valueassociatedValues
<- list ofAssociatedValue
annotations
<- dictionary with configured annotations
**AssociatedValue**. Properties:
localName
<- name to use to construct value, i.e.value
inFoo.foo(value: ...)
externalName
<- name to use when binding value, i.e.value
orother
inenum Foo { case foo(value: ..., other: ... )}
. Will use index as a fallbacktypeName
<- name of type of associated value (TypeName)actualTypeName
<- returnstypeName.actualTypeName
or if it'snil
returnstypeName
unwrappedTypeName
<- shorthand fortypeName.unwrappedTypeName
isOptional
<- shorthand fortypeName.isOptional
isImplicitlyUnwrappedOptional
<- shorthand fortypeName. isImplicitlyUnwrappedOptional
isTuple
<- shorthand fortypeName.isTuple
isClosure
<- shorthand fortypeName.isClosure
isArray
<- shorthand fortypeName.isArray
**Variable**. Properties:
name
<- Nametype
<- type of the variable, if knowntypeName
<- returns name of the type (TypeName)actualTypeName
<- returnstypeName.actualTypeName
or if it'snil
returnstypeName
unwrappedTypeName
<- shorthand fortypeName.unwrappedTypeName
isOptional
<- shorthand fortypeName.isOptional
isImplicitlyUnwrappedOptional
<- shorthand fortypeName. isImplicitlyUnwrappedOptional
isComputed
<- whether is computedisStatic
<- whether is static variableisTuple
<- shorthand fortypeName.isTuple
isClosure
<- shorthand fortypeName.isClosure
isArray
<- shorthand fortypeName.isArray
readAccess
<- what is the protection access for reading?writeAccess
<- what is the protection access for writing?attributes
<- variable attributes, i.e.var.attributes.NSManaged
annotations
<- dictionary with configured annotations
**Method**. Properties:
name
<- full name of the method including generic constraints, i.e.func foo(bar: Bar)
orfoo<T>(bar: T)
selectorName
<- selector name of the method, i.e forfunc foo(bar: Bar) -> Bar
it isfoo(bar:)
, forfunc foo<T>(bar: T)
it isfoo(bar:)
shortName
<- short method name, i.e. forfunc foo(bar: Bar) -> Bar
it isfoo
, forfunc foo<T>(bar: T)
it isfoo<T>
parameters
<- list of all method parametersreturnType
<- return type, if known, for initializers - containing typereturnTypeName
<- return type name (TypeName). Will beVoid
for methods without return value or empty string for initializers. For generic methods can include generic constraints specified withwhere
, i.e.func foo<T>(bar: T) -> T where T: Equatable
it isT where T: Equatable
actualReturnTypeName
<- returnsreturnTypeName.actualTypeName
or if it'snil
returnsreturnTypeName
unwrappedReturnTypeName
<- shorthand forreturnTypeName.unwrappedTypeName
isOptionalReturnType
<- shorthand forreturnTypeName.isOptional
isImplicitlyUnwrappedOptionalReturnType
<- shorthand forreturnTypeName. isImplicitlyUnwrappedOptional
accessLevel
<- method access levelisStatic
<- whether method is staticisClass
<- whether method is class (can be overriden by subclasses)isInitializer
<- whether method is an initializerisFailableInitializer
<- whether method is failable initializerattributes
<- method attributes, i.e.method.attributes.discardableResult
annotations
<- dictionary with configured annotations
**MethodParameter**. Properties:
name
<- parameter nameargumentLabel
<- argument label (external name), if not set will be eqal toname
type
<- type of parameter, if knowntypeName
<- parameter type name (TypeName)actualTypeName
<- returnstypeName.actualTypeName
or if it'snil
returnstypeName
unwrappedTypeName
<- shorthand fortypeName.unwrappedTypeName
isOptional
<- shorthand fortypeName.isOptional
isImplicitlyUnwrappedOptional
<- shorthand fortypeName. isImplicitlyUnwrappedOptional
isTuple
<- shorthand fortypeName.isTuple
isClosure
<- shorthand fortypeName.isClosure
isArray
<- shorthand fortypeName.isArray
typeAttributes
<- parameter's type attributes, shorthand fortypeName.attributes
, i.e.param.typeAttributes.escaping
**TypeName**. Properties:
name
<- type nameactualTypeName
<- if given type is a typealias or contained type name will contain actual fully qualified type nameunwrappedTypeName
<- returns name of the type, unwrapping the optional e.g. for variable with typeInt?
this would returnInt
, removing attributes and generic constraintsisOptional
<- whether is optionalisImplicitlyUnwrappedOptional
<- whether is implicitly unwrapped optionalisVoid
<- whether type is Void (Void
or()
)isTuple
<- whether given type is a tupletuple
<- returns information about tuple type (TupleType) based onactualTypeName.unwrappedTypeName
isClosure
<- shorthand fortypeName.isClosure
isArray
<- shorthand fortypeName.isArray
attributes
<- type attributes, i.e.typeName.attributes.escaping
**TupleType**. Properties:
name
<- type nameelements
<- returns tuple elements information (TupleElement)
**TupleElement**. Properties:
name
<- element nametype
<- type of element, if knowntypeName
<- element type name (TypeName)unwrappedTypeName
<- shorthand fortypeName.unwrappedTypeName
isOptional
<- shorthand fortypeName.isOptional
isTuple
<- shorthand fortypeName.isTuple
isClosure
<- shorthand fortypeName.isClosure
**ArrayType**. Properties:
name
<- type nameelementType
<- array element type, if knownelementTypeName
<- array element type name (TypeName)
{{ name|upperFirst }}
- makes first letter inname
uppercase{{ name|replace:"substring","replacement" }}
- replaces occurances ofsubstring
withreplacement
inname
(case sensitive){% if name|contains:"Foo" %}
- check ifname
contains arbitrary substring, can be negated with!
prefix.{% if name|hasPrefix:"Foo" %}
- check ifname
starts with arbitrary substring, can be negated with!
prefix.{% if name|hasSuffix:"Foo" %}
- check ifname
ends with arbitrary substring, can be negated with!
prefix.static
,instance
,computed
,stored
,tuple
- can be used on Variable[s] as filter e.g.{% for var in variables|instance %}
, can be negated with!
prefix.static
,instance
,class
,initializer
- can be used on Method[s] as filter e.g.{% for method in allMethods|instance %}
, can be negated with!
prefix.enum
,class
,struct
,protocol
- can be used for Type[s] as filter, can be negated with!
prefix.based
,implements
,inherits
- can be used for Type[s], Variable[s], Associated value[s], can be negated with!
prefix.count
- can be used to get count of filtered arrayannotated
- can be used on Type[s], Variable[s], Method[s] and Enum Case[s] to filter by annotation, e.g.{% for var in variable|annotated: \"skipDescription\"%}
, can be negated with!
prefix.
Sourcery supports annotating your classes and variables with special annotations, similar how attributes work in Rust / Java
/// sourcery: skipPersistence
/// Some documentation comment
/// sourcery: anotherAnnotation = 232, yetAnotherAnnotation = "value"
/// Documentation
var precomputedHash: Int
If you want to attribute multiple items with same attributes, you can use section annotations:
/// sourcery:begin: skipEquality, skipPersistence
var firstVariable: Int
var secondVariable: Int
/// sourcery:end
- Multiple annotations can occur on the same line
- You can add multiline annotations
- You can interleave annotations with documentation
- Sourcery scans all
sourcery:
annotations in the given comment block above the source until first non-comment/doc line
- simple entry, e.g.
sourcery: skipPersistence
- key = number, e.g.
sourcery: another = 123
- key = string, e.g.
sourcery: jsonKey = "json_key"
{% ifnot variable.annotations.skipPersistence %}
var local{{ variable.name|capitalize }} = json["{{ variable.annotations.jsonKey }}"] as? {{ variable.typeName }}
{% endif %}
Sometimes it is desirable to only generate code if there's at least one field annotated.
{% if type.variables|annotated:"jsonKey" %}{% for var in type.variables|instance|annotated:"jsonKey" %}
var local{{ var.name|capitalize }} = json["{{ var.annotations.jsonKey }}"] as? {{ var.typeName }}
{% endfor %}{% endif %}
Sourcery supports inline code generation, you just need to put same markup in your code and template, e.g.
// in template:
{% for type in types.all %}
// sourcery:inline:{{ type.name }}.TemplateName
// sourcery:end
{% endfor %}
// in source code:
class MyType {
// sourcery:inline:MyType.TemplateName
// sourcery:end
}
Sourcery will generate the template code and then perform replacement in your source file. Inlined generated code is not parsed to avoid chicken-egg problem.
Sourcery supports generating code in a separate file per type, you just need to put file
annotation in a template, e.g.
{% for type in types.all %}
// sourcery:file:Generated/{{ type.name}}+TemplateName
// sourcery:end
{% endfor %}
Sourcery will generate the template code and then write its annotated parts to corresponding files. In example above it will create Generated/<type name>+TemplateName.generated.swift
file for each of scanned types.
Binary form
The easiest way to download the tool right now is to just grab a newest `.zip` distribution from [releases tab](https://github.com/krzysztofzablocki/Sourcery/releases).Via CocoaPods
If you're using CocoaPods, you can simply add pod 'Sourcery' to your Podfile.This will download the Sourcery binaries and dependencies in Pods/
.
You just need to add $PODS_ROOT/Sourcery/bin/sourcery {source} {templates} {output}
in your Script Build Phases.
Via Swift Package Manager
If you're using SwiftPM, you can simply add 'Sourcery' to your manifest.Sourcery is placed in Packages
.
After your first swift build
, you can run .build/debug/sourcery {source} {templates} {output}
.
From Source
You can clone it from the repo and just run `Sourcery.xcworkspace`.Sourcery is a command line tool sourcery
:
$ ./sourcery <source> <templates> <output> [--args arg1=value,arg2]
Arguments:
- source - Path to a source swift files.
- templates - Path to templates. File or Directory.
- output - Path to output. File or Directory.
- args - Additional arguments to pass to templates. Each argument can have explicit value or will have implicit
true
value. Arguments should be separated with,
without spaces. Arguments are accessible in templates viaargument.name
Options:
--watch
[default: false] - Watch both code and template folders for changes and regenerate automatically.--verbose
[default: false] - Turn on verbose logging for ignored entities
Contributions to Sourcery are welcomed and encouraged!
It is easy to get involved. Please see the Contributing guide for more details.
A list of contributors is available through GitHub.
To give clarity of what is expected of our community, Sourcery has adopted the code of conduct defined by the Contributor Covenant. This document is used across many open source communities, and I think it articulates my values well. For more, see the Code of Conduct.
Sourcery is available under the MIT license. See LICENSE for more information.
This tool is powered by
- SourceKitten by JP Simard
- Stencil and few other libs by Kyle Fuller
Thank you! for:
- Mariusz Ostrowski for creating the logo.
- Artsy Eidolon team, because we use their codebase as a stub data for performance testing the parser.
- Olivier Halligon for showing me his setup scripts for CLI tools which are powering our rakefile.
If you want to generate code for asset related data like .xib, .storyboards etc. use SwiftGen. SwiftGen and Sourcery are complementary tools.
Make sure to check my other libraries and tools, especially:
- KZPlayground - Powerful playgrounds for Swift and Objective-C
- KZFileWatchers - Daemon for observing local and remote file changes, used for building other developer tools (Sourcery uses it)
You can follow me on twitter for news/updates about other projects I am creating.