Useful Custom Rules for SwiftLint

SwiftLint and SwiftFormat together cover 80% of recurring code-review comments. They’re great, use them.

To cover the last 20%, you can add custom rules to your SwiftLint configuration file .swiftlint.yml. Below are some of the custom rules I developed over time.

Auto-generated Leftovers

When you use Xcode’s templates to create a new class, you’ll get some auto-generated code that you don’t need, adding noise and making it harder to find the useful code. Just delete it.

custom_rules:
  auto_generated_leftovers:
    regex: 'func [^\n]*\{\n(\s*super\.[^\n]*\n(\s*\/\/[^\n]*\n)*|(\s*\/\/[^\n]*\n)+)\s*\}'
    message: "Delete auto-generated functions that you don't use"

Numbers Are a Code Small

You probably know that you shouldn’t put raw numbers in code, right? Only use constants and variables, right? Well, whenever I run the following rule on a code-base, I get a surprising number of warnings.

custom_rules:
  numbers_smell:
    regex: '(return |case |\w\(|: |\?\? |\, |== |<=? |>=? |\+= |\-= |\/= |\*= |%= |\w\.\w+ = )\(*-?\d{2,}'
    message: "Numbers smell; define a constant instead."
    excluded: '.*Tests/'

NSLocalizedString

Localization is not a priority in many projects, but when you suddenly need to convert all strings to localized strings – that’s a lot of work, with a good chance of missing some strings.

The following rule is not perfect, but it seems to work well enough.

custom_rules:
  non_localized_string:
    regex: '(?<!NSLocalizedString\(|fatalError\(|assertionFailure\(|preconditionFailure\(|assert\(false, |format: |separator: |deprecated, message: |\w|\")(?:"[^" \n]+ [^"\n]*"|"[[:upper:]][[:lower:]]+"|""".*?""")'
    message: "Wrap string in NSLocalizedString()"
    match_kinds: string
    excluded: '.*Tests/'

Comparing to Bool

I’m not sure why anyone would do such a thing, but some people write if (isHidden == true) instead of simply if isHidden. I don’t even know in which language that would make sense, but definitely not Swift. Therefore:

custom_rules:
  already_true:
    regex: "== true"
    message: "Don't compare to true, just use the bool value."
  already_bool:
    regex: "== false"
    message: "Don't compare to false, just use !value."

Commented-out Code

If you use source-control, there is no reason to leave old commented-out code – you can always find it in your repository. Just clean it up.

custom_rules:
  commented_code:
    regex: '(?&lt;!:|\/)\/\/\h*[a-z.](?!wiftlint)'
    message: "Comment starting with lowercase letter - did you forget to delete old code?"
  multiline_commented_code:
    regex: '^\s*[a-z]'
    match_kinds: comment
    message: "Comment starting with lowercase letter - did you forget to delete old code?"