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.

5 (6) · 4 Reviews

Download materials
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

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:

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.

Using a regular expression to change multiple code instances together

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.

Note: You can learn more about regular expressions and the new Regex data type from Swift Regex Tutorial: Getting Started.

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:

  1. You start by creating an identifier for the new rule and include all its properties underneath.
  2. name: The name for this rule.
  3. regex: You define the regular expression for the violation you want to search for.
  4. 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.
  5. message: The message you want to show to describe the issue.
  6. severity: Set to error or warning.

Build the project to see this new rule in action:

The new custom rule showing two warnings in Xcode

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. :]