Tooling: swiftlint and swiftgen

By Will Braynen

Illustration by Donna Blumenfeld

Illustration by Donna Blumenfeld

Bigger Picture and a How To

When working with any Swift Xcode project, I always add SwiftLint and SwiftGen to my Xcode build steps.  When working with a messy codebase, I first try to autocorrect the codebase with SwiftLint and correct what's left by hand.  If that fails and I really need to grandfather in existing files, then I have my build step only lint files that are staged for a git commit.  In this post, I will describe how I go about doing this with SwiftLint.  (For SwiftGen, see this blog post by Logan Gauthier.)

 

Bigger picture: TOOLING AND STATIC CODE ANALYSIS

When, in high school, I was lucky to get away from the mugginess of New York City summers by sanding, drywalling and installing skylights in Pennsylvania and upstate New York, I had a toolbox and a tool belt.  The toolbox had tools, some of which would go in my tool belt.  Perhaps the tools didn't change as frequently as they do in software engineering and instead of Home Depot I now go to github or homebrew—plus I expect most of my tools to somehow be free now—but the idea was the same.  I love me some good tooling.

There are two tools I always set up right away whenever I work with a new Swift Xcode project: SwiftLint and SwiftGen.  I like turning runtime errors into compile-time errors (swiftlint and swiftgen) and I like auto-enforcing a style guide (swiftlint).  Swiftgen is to turn "stringly typed" code into enums, so that if, for example, you accidentally remove an image from your asset catalog, your code will no longer compile instead of crashing at runtime in production.  Swiftlint is a linter.  

IMG-0668.JPG

Linting is a form of static code analysis—swiftlint does more than just enforce style guidelines (e.g. it flags forced casts and forced unwraps which can cause a crash in production)—and static code analysis is code analysis that is performed without actually executing the code.  The human analog of static code analysis is examining the code by looking at it instead of playing around with the executable.

At the org level, a practice I like is making available a default swiftlint yaml to give some reasonable default rules in case swiftlint's defaults won't do and then allowing teams to deviate from these defaults as they see fit.  (This of course means not including a swiftlint yaml in your org's pods unless it's at the example level.)

You can run swiftlint and swiftgen just locally as part of your local build process—see instructions below.  Or you could also make them part of your CI/CD pipeline by installing them on your build server, like your Jenkins box or BuddyBuild.

 

How to: adding SwiftLint to YOUR Xcode project

 

First do this

Install SwiftLint.  Official instructions can be found on SwiftLint's github page.  But basically, it's this:

$ brew install swiftlint
$ brew upgrade swiftlint

 

Second do this

Add a build-phase step to your Xcode project:

Screen Shot 2017-07-17 at 12.45.27 PM.png
Screen Shot 2017-07-17 at 12.46.49 PM.png

The shell script for step 5 above:

# swiftlint everything

if which swiftlint >/dev/null; then
  swiftlint
else
  echo "warning: SwiftLint not installed."
fi

Step 6 is optional.  The user-friendly name "SwiftLint"—or whatever you want to name this build step—is for you, not for the machine.  (SwiftGen would, similarly, be another build step, just like SwiftLint.)

 

Third you could try this

If inheriting an ugly codebase, you could try to autocorrect by opening the terminal and typing in `swiftlint autocorrect`:

$ swiftlint autocorrect

But of course then you might introduce all kinds of regressions, so I personally would be too risk averse to do this on a real (e.g. large and legacy) production app and so would just skip to step four below. But if you were breckless enough to do this (brave/reckless?), here is an example of what you might see:

If any swiftlint errors or warnings are still showing up in Xcode, then, assuming a manageable number remains, try to correct the rest by hand.

 

Fourth, if you still need to, do this

If you need to grandfather in old ugly files because you have a gazillion swiftlint warnings, then change your build-step script in Xcode to this:

# swiftlint staged files only

if which swiftlint > /dev/null; then
    git diff --staged --name-only | grep .swift | while read filename; do
        swiftlint lint --path "$filename";
    done
else
    echo "warning: SwiftLint not installed."
fi

NB: `git diff --staged` and `git diff --cached` mean the same thing.

From the terminal, git status will show you which files have been staged, so that you can tell which files will get linted. Beware that SourceTree (or a similar GUI client) might not always reflect this accurately in real time, although ideally the following would always be accurate:

Screen Shot 2017-07-26 at 5.42.30 PM.png

And if you don't want to lint a staged file, simply unstage it. From the terminal, git reset will unstage.

Bon appetit!