By becoming a paid supporter, you'll not only receive the latest content straight to your inbox, but you'll also contribute to the growth and maintenance of over 20 open-source projects used by 80,000+ teams—including industry giants like Apple, Disney, Airbnb, and more.
Join here
Even though my Sourcery has become the industry standard for anything related to metaprogramming in Swift, many people are still not fully leveraging its power. Many projects use it for primary use-cases like generating Mocks.
Larger projects like Airbnb, The New York Times, or The Browser Company use custom templates to enable workflows and architectures that are impossible to achieve in standard Swift.
I'll cover many more exciting Sourcery use-cases in future articles but let's start by giving you a few examples of what you probably didn't consider using Sourcery.
Sourcery AST primer
We usually access AST via one of the top-level objects Sourcery exposes. Let's quickly go over the ones we will be using in this article:
- types <- Types in the codebase, available via custom accessors
- all <- lists all types except protocols
- protocols
- classes/structs/enums <- filter to specific kind of type
- based <- Types based on any other type, grouped by its name, even if they are not known.
types.based.MyType
returns a list of types based onMyType
- implementing/inheriting <- similar to
based
but for known types (those defined in the codebase)
- type <- access all types by their name
Examples
1. Finding all classes that could be final
The algorithm for this is straightforward:
- For all classes in the codebase
- Filter out those that aren't final or open
- If the type doesn't serve as a base for other types it can be marked final
What's interesting, though, is that Sourcery can generate anything, not just Swift code, so we can step further than just finding the classes: we can create a bash script that will rewrite your codebase.
- Find the class definition via grep
git grep -lz 'class <%= type.name %>'
- Pipe it to perl regex replacement
| xargs -0 perl -i'' -pE "s/class <%= type.name %>(?=\s|:)/final class <%= type.name %>/g"
Full template
2. Linting
For The Browser Company, I've implemented a lot of custom dev tooling, and I wanted to inform engineers if they didn't leverage it in their PRs.
One example is my LifetimeTracker. I want all view controller subclasses to conform to it. Standard linting isn't enough to verify this accurately since someone can do to the protocol via extension and/or in separate files from the type definition.
Sourcery can list the types that don't conform to a specific protocol:
{% for type in types.classes|based:"NSViewController"|!implements:"LifetimeTrackable" %}
// - {{ type.name }}
{% endfor %}
I add this to the project, and the linter part becomes relatively easy. I verify if the number of lines in a given PR has increased. If so, it means a class was added that didn't conform but should.
3. Finding classes conforming to a protocol
Back in Objective-C, you could leverage runtime to find all classes that conformed to a specific protocol, this functionality doesn't work in standard Swift, but with Sourcery, you can do even more.
Many bigger codebases (e.g., Airbnb) use a pattern where they find all types conforming to a given protocol and automatically register them in the application target.
func registerAllComponents() {
{% for type in types.based.Component|!protocol %}
ComponentsRegistry.register({{type.name}}.self)
{% endfor %}
}
Any time the developers add a new Component
, it will immediately register in the application without any manual code changes.
Conclusion
These are just a couple of examples of non-standard Sourcery use-cases. Sourcery get's even more powerful with Sourcery Pro's Xcode extension and it's powerful editor.
In the future, I'll cover specific use-cases in-depth, and you'll be able to see some real magic, in the meantime if you have any questions about Sourcery or your use-cases, I'm always happy to help.