SwiftLint in Depth
Learn how to use and configure SwiftLint in detail, as well as how to create your own rules in SwiftLint for your project. By Ehab Amer.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
SwiftLint in Depth
30 mins
- Getting Started
- What Is SwiftLint?
- How SwiftLint Works
- Installing SwiftLint
- Installing Homebrew
- Installing SwiftLint
- Using Terminal
- Xcode Integration
- What Are SwiftLint Rules?
- Warnings and Errors
- Using Rules File
- Structure of the Rules File
- Excluded List
- Disabling Rules
- Configuring Rules
- Disabling Rules Through Code
- Fixing the Violations
- Enabling More Rules
- Make Your Own Rules
- Remote Rules
- Where to Go From Here?
Enabling More Rules
Your team has agreed to add a few more rules on top of the defaults and not all of them with the default configurations of SwiftLint:
- indentation_width, to have an indentation of two spaces and not four.
- force_unwrapping
- redundant_type_annotation
- force_try
- operator_usage_whitespace
Add the following to the rules file:
opt_in_rules:
- indentation_width
- force_unwrapping
- redundant_type_annotation
- force_try
- operator_usage_whitespace
indentation_width:
indentation_width: 2
Build the project. You see eight new warnings. Only one is about indentation. The rest are because of force unwrapping.
Let’s fix the indentation one first. Tap the indentation warning to open ProductionsDataProvider.swift. Go to the warning there, then align return []
with the catch
above it:
} catch {
return []
}
A few of the force castings in ProductionYearInfo.swift are because some Int
initializations are force-unwrapped. Int(:)
can produce nil
if the string passed is not a number. For any reason, if the value passed to the constructor had an alphabetical character, the produced value would be nil
, and the force unwrapping would cause a crash.
You’ll fix this using the nil-coalescing operator. But you’ll try a trick to solve more than one warning with a single search and replace, using regular expressions.
From the project navigator column, select the Find tab. Change Find to Replace and from Text to Regular Expression. In the first text field, enter Int\((.+)\)!
and in the second one, enter Int($1) ?? 0
.
By keeping the editing cursor on the first text field and pressing return on the keyboard, Xcode will search and won’t apply the replacement. This is handy if you want to check before pressing the “Replace all” button.
You’ll have five search results. All have a force unwrapping on an Int(:)
call. Replace all.
Build the project to make sure everything is OK. The build succeeds, and you have only two warnings left. How did this regex magic work?
The expression you entered Int\((.+)\)!
looks for any text starting with Int(
. Because the round brackets are actual characters used in regular expressions, you must escape them.
The inner set of parentheses is a capture group. The matched expression inside is stored for later use, and you access it with $1
, which you entered in the replacement string. The rest of the expression is the closing parentheses and the force unwrapping operator, )!
.
The capture group allows you to store the property sent to the integer constructor and reuse this property in the new replacement string. You only want to focus on force-unwraps of Int(:)
. If you search for )!
only across the project, you’ll change places you shouldn’t.
As for the last two warnings, find the first in PremieredOnInfo.swift and replace the offending code with:
let date = formatter.date(from: dateString) ?? Date()
Then find the second in ProductionItemView.swift and replace the offending code with:
Text("\(String(format: "%.1f", productionItem.imdbRating ?? 0))")
All the warnings are gone!
Make Your Own Rules
Another cool feature SwiftLint supports is the ability to create your own rules. SwiftLint treats rules you create the same way it handles its built-in rules. The one thing you’ll need is to create a regular expression for it.
The rule your team wants to apply is about declaring empty arrays and dictionaries. You want to define the type, and it should not rely on inference:
// Not OK
var array = [Int]()
var dict = [String: Int]()
// OK
var array: [Int] = []
var dict: [String: Int] = [:]
Add the following to the rules file:
custom_rules:
array_constructor: # 1
name: "Array/Dictionary initializer" # 2
regex: '[let,var] .+ = (\[.+\]\(\))' # 3
capture_group: 1 # 4
message: "Use explicit type annotation when initializing empty arrays and dictionaries" # 5
severity: warning # 6
custom_rules is another section in the rules file where you can declare your own set of rules.
Here is a step-by-step description of the above custom rule:
- You start by creating an identifier for the new rule and include all its properties underneath.
- name: The name for this rule.
- regex: You define the regular expression for the violation you want to search for.
- capture_group: If part of the regular expression match is where the violation is and you’re using capture groups to focus on it, you specify the number of the capture group here. If you’re not using capture groups, you don’t need to include this property.
- message: The message you want to show to describe the issue.
- severity: Set to error or warning.
Build the project to see this new rule in action:
To fix these two warnings, you have a direct text replacement for:
// In ProductionsDataProvider.swift
var marvelProductions = [MarvelProductionItem]()
// In ProductionsListView.swift
var productionsList = [MarvelProductionItem]()
To:
var marvelProductions: [MarvelProductionItem] = []
var productionsList: [MarvelProductionItem] = []
Remote Rules
SwiftLint has an awesome feature that helps keep rules centralized for the whole team. The rules file doesn’t need to be beside the project file or named .swiftlint.yml. You can move the file to anywhere you want on your machine. You even can store it on a server and pass its path as an argument to the swiftlint
command:
swiftlint --config [yml file path or url]
Why not give it a try?
Open Terminal and navigate to your project’s path. Then run this command:
mv .swiftlint.yml ~/swiftlintrules.yml
This moves .swiftlint.yml from the current directory to the root of the user folder and renames the file to swiftlintrules.yml.
Go back to your project in Xcode and update the run script to the following:
export PATH="$PATH:/opt/homebrew/bin"
if which swiftlint > /dev/null; then
swiftlint --config ~/swiftlintrules.yml
else
echo "warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint"
fi
Build the project. Unfortunately, it’ll give some warnings and errors. Don’t panic. Files reporting violations are from the code files in the folder you excluded earlier.
In the new rules file, change the exclude section to the following:
excluded:
- ${PWD}/DerivedData
All you did was have the exclude folders with a full path. PWD, or Print Working Directory, translates to the project directory when Xcode runs the command.
Build the project again. It’ll succeed. :]