We released SwiftLint 0.14 over the weekend, and it’s probably our biggest release yet! We added 14 new rules, and shipped CocoaPods, SwiftPM, and Linux support. We’ll dive into the rule changes first, and then explore the optimizations we made to keep the SwiftLint platform useful as Swift continues to grow and change.
We’ve marked the locations of errors and warnings with arrows (↓
) so you can see without having to run these snippets yourself!
New Rules
Dynamic Inline (Daniel Duan)
It’s always nice to see rules added to SwiftLint that aren’t just stylistic, but prevent real bugs from creeping into your code.
This rule will trigger if you mark a declaration with both the dynamic
and @inline(__always)
attributes, since that combination leads to undefined dispatch behavior.
class C {
@inline(__always) dynamic ↓func f() {}
}
Number Separator (Marcelo Fabri)
This rule encourages the use of a number separator when writing large number literals, preferring 10_000_000
over 10000000
. I think you’ll agree this makes for more readable code. It’ll also catch misleading separations, like 10_000_00
!
var foo = ↓10_0
foo = ↓1000
foo = ↓1__000
foo = ↓1.0001
foo = ↓1_000_000.000000_1
foo = ↓1000000.000000_1
File Header (Marcelo Fabri)
This opt-in, configurable rule let you specify a format that all Swift file headers should or shouldn’t conform to.
For example, all SwiftLint files should use the following template:
//
// FileHeaderRule.swift
// SwiftLint
//
// Created by Marcelo Fabri on 27/11/16.
// Copyright © 2016 Realm. All rights reserved.
//
So we include a regular expression to validate in our .swiftlint.yml
file.
You can configure this rule to require or forbid certain strings or regular expressions. Some users already use this to prevent Copyright
from being included in their headers. To each their own. ¯\_(ツ)_/¯
Redundant String Enum (Marcelo Fabri)
Given that Swift implicitly assigns raw enum values, it’s a bit redundant to duplicate each String
enum member’s name as its associated value. This rule will trigger when all explicitly assigned values are character-for-character equal to the member names. If even one of the names differs, this rule won’t trigger so you can stay consistent within your enum and keep your sanity.
enum Numbers: String {
case one = ↓"one", case two = ↓"two"
// and
case one = ↓"one", two = ↓"two"
// and even
case one, two = ↓"two"
}
Attributes (Marcelo Fabri)
This opt-in & configurable rule validates if an attribute (@objc
, @IBOutlet
, @discardableResult
, etc) is in the “correct” position.
The correct position in this case being what’s been established as convention by the Swift community:
- If the attribute is
@IBAction
or@NSManaged
, it should always be on the same line as the declaration. - If the attribute has parameters, it should always be on the line above the declaration.
- Otherwise:
- If the attribute is applied to a variable, it should be on the same line.
- If it’s applied to a type or function, it should be on the line above.
- If it’s applied to an import (the only option is
@testable import
), it should be on the same line.
You can also configure what attributes should be always on a new line or on the same line as the declaration with the always_on_same_line
and always_on_line_above
keys.
Empty Parentheses with Trailing Closure (Marcelo Fabri)
In Swift, both [1, 2].map { $0 + 1 }
and [1, 2].map() { $0 + 1 }
are valid syntaxes! Using these interchangeably is at best inconsistent and at worst confusing. This rule will trigger when empty parentheses are used:
[1, 2].map↓( ) { $0 + 1 }
Closure Parameter Position (Marcelo Fabri)
Closure parameters should be on the same line as opening brace. For example:
// Prefer
[1, 2].map { number in
number + 1
}
// Over
[1, 2].map {
↓number in
number + 1
}
Prohibited Super (Aaron McTavish)
Some of Apple’s methods should not call super
when overriden, according to their documentation. This opt-in rule will trigger whene one of these methods does call super
in its implementation. For example:
NSFileProviderExtension.providePlaceholder(at:completionHandler:)
NSTextInput.doCommand(by:)
NSView.updateLayer()
UIViewController.loadView()
class VC: UIViewController {
override func loadView() ↓{
super.loadView()
}
}
Empty Parameters & Void Return (Marcelo Fabri & JP Simard)
Apple and the Swift community has settled on this style:
FWIW, we’ve recently decided to standardize on
() -> Void
(generally,()
for parameters andVoid
for return types) across all of our documentation.
This rule will trigger (and automatically correct) incorrect usages:
// Prefer
let abc: () -> Void = {}
// Over
let abc: ↓Void -> Void = {}
let abc: () -> ↓()
Operator Usage Whitespace (Marcelo Fabri)
This opt-in rule will trigger if an operator isn’t surrounded by a single whitespace on either side when used:
// Prefer
let foo = 1 + 2
let v8 = 1 << 6
// Over
let foo = 1+2
let foo=1 + 2
let v8 = 1 << (6)
Unused Closure Parameter (Marcelo Fabri & JP Simard)
Here’s another rule that can help prevent bugs, or at least make your code mode self-documenting. This rule will trigger (and automatically correct!) any unused parameter in a closure by replacing them with _
. For example:
[1, 2].map { ↓number in
return 3
}
[1, 2].something { number, ↓idx in
return number
}
Unused Enumerated (Marcelo Fabri)
Here’s another rule that can help prevent bugs, or at least make your code mode self-documenting. This rule will trigger if the indexes aren’t used iterating over the results of an .enumerated()
call. For example:
for (↓_, foo) in bar.enumerated() {}
// or
for (↓_, foo) in abc.something().enumerated() {}
Platform Changes
Port to Swift 3
We designed SwiftLint to work with any Swift version you’ll throw at it. That means our very first releases from a year ago work out of the box with Swift 3, and that you can use our very latest releases with older Swift versions.
Learn more about how SwiftLint finds which toolchain to lint with here.
Thanks to this design choice, we weren’t in a rush to rewrite all of SwiftLint in Swift 3.
However, this release is the first after a large effort to port it to Swift 3. This should make it easier to update dependencies regularly, and should lower the barrier to entry to contributors since they won’t need to install older Xcode versions.
Swift Package Manager & Linux Support
Because the latest releases of the Swift Package Manager and the Swift Linux Distributions require Swift 3, we couldn’t reasonably offer support for these installation methods until we ported all of SwiftLint and its dependencies to Swift 3. This took a few months, but now the wait is over!
You can now use SwiftLintFramework
in your SwiftPM packages.
Moving to Swift 3 also meant that we could pull in the SourceKitten work that happened from June to October to support Linux.
So SwiftLint now builds and passes most tests on Linux using the Swift Package Manager with Swift 3!
We hope this will make it easier to include SwiftLint in your infrastructure without having to maintain Mac servers.
This requires libsourcekitdInProc.so
to be built and located in /usr/lib
, or in another location specified by the LINUX_SOURCEKIT_LIB_PATH
environment variable.
Unfortunately, SourceKit isn’t included in Swift Linux distributions by default (yet) but a preconfigured Docker image is available on Docker Hub by the ID of norionomura/sourcekit:302
.
CocoaPods Support
We’ve added support for CocoaPods for both SwiftLintFramework
and the swiftlint
CLI executable.
Add pod 'SwiftLintFramework'
to your Podfile if you’re building a macOS app and wish to incorporate SwiftLint’s functionality via the framework APIs. Or spec.dependency 'SwiftLintFramework
to your podspec if you’re building your own pod.
Add pod 'SwiftLint', '= 0.14'
to install a specific version of the SwiftLint CLI executable in your Pods/
directory. This follows the pattern introduced by SwiftGen to install specific and pinned versions of SwiftLint, so your CI builds will never break again because SwiftLint released a new version with new rules!
Other Enhancements
Denis Lebedev added FunctionParameterCountRule
now ignores initializers (in #544), Michał Kałużny added EmojiReporter
, and Marcelo Fabri made sure thattype_name
rule now validates both typealias
and associatedtype
(in #49 and #956).
Bug Fixes
Finally, there are quite a few bug fixes in this release, including some that deal with multi-byte characters in strings. Marcelo Fabri improved our unit tests to prevent bugs like these from creeping back in. To see the full list of the fixes our contributors worked on, check out our changelog. Our thanks go out to them!
This release was made possible by a large number of community contributions. A big thanks to Aaron McTavish, Daniel Duan, Denis Lebedev, Fabian Ehrentraud, JP Simard, Marcelo Fabri, Michał Kałużny, Norio Nomura and Phil Webster! 👏
We hope you’ll contribute next!
Receive news and updates from Realm straight to your inbox