{"id":288,"date":"2019-04-29T18:35:23","date_gmt":"2019-04-29T18:35:23","guid":{"rendered":"http:\/\/ootips.org\/yonat\/?p=288"},"modified":"2025-07-26T12:21:47","modified_gmt":"2025-07-26T12:21:47","slug":"useful-custom-rules-for-swiftlint","status":"publish","type":"post","link":"https:\/\/ootips.org\/yonat\/useful-custom-rules-for-swiftlint\/","title":{"rendered":"Useful Custom Rules for SwiftLint"},"content":{"rendered":"<p><a href=\"https:\/\/github.com\/realm\/SwiftLint\" target=\"_blank\" rel=\"noopener noreferrer\">SwiftLint<\/a> and <a href=\"https:\/\/github.com\/nicklockwood\/SwiftFormat\" target=\"_blank\" rel=\"noopener noreferrer\">SwiftFormat<\/a> together cover 80% of recurring code-review comments. They&#8217;re great, use them.<\/p>\n<p>To cover the last 20%, you can add custom rules to your SwiftLint configuration file <code>.swiftlint.yml<\/code>. Below are some of the custom rules I developed over time.<\/p>\n<h2>Auto-generated Leftovers<\/h2>\n<p>When you use Xcode&#8217;s templates to create a new class, you&#8217;ll get some auto-generated code that you don&#8217;t need, adding noise and making it harder to find the useful code. Just delete it.<\/p>\n<pre class=\"lang:yml decode:true\">custom_rules:\n  auto_generated_leftovers:\n    regex: 'func [^\\n]*\\{\\n(\\s*super\\.[^\\n]*\\n(\\s*\\\/\\\/[^\\n]*\\n)*|(\\s*\\\/\\\/[^\\n]*\\n)+)\\s*\\}'\n    message: \"Delete auto-generated functions that you don't use\"\n<\/pre>\n<h2>Numbers Are a Code Small<\/h2>\n<p><b>Update:<\/b> Just use <code>no_magic_numbers<\/code> rule, available since 0.50.0.<\/p>\n<p><b>Old version:<\/b> <del>You probably know that you shouldn&#8217;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.<\/del><\/p>\n<pre class=\"lang:yml decode:true \" style=\"text-decoration: line-through\">custom_rules:\n  numbers_smell:\n    regex: '(return |case |\\w\\(|: |\\?\\? |\\, |== |&lt;=? |&gt;=? |\\+= |\\-= |\\\/= |\\*= |%= |\\w\\.\\w+ = )\\(*-?\\d{2,}'\n    message: \"Numbers smell; define a constant instead.\"\n    excluded: '.*Tests\/'\n<\/pre>\n<h2>NSLocalizedString<\/h2>\n<p>Localization is not a priority in many projects, but when you suddenly need to convert all strings to localized strings &#8211; that&#8217;s a lot of work, with a good chance of missing some strings.<\/p>\n<p>The following rule is not perfect, but it seems to work well enough.<\/p>\n<pre class=\"lang:yml decode:true \">custom_rules:\n  non_localized_string:\n    regex: '(?&lt;!NSLocalizedString\\(|fatalError\\(|assertionFailure\\(|preconditionFailure\\(|assert\\(false, |format: |separator: |deprecated, message: |\\w|\\\")(?:\"[^\" \\n]+ [^\"\\n]*\"|\"[[:upper:]][[:lower:]]+\"|\"\"\".*?\"\"\")'\n    message: \"Wrap string in NSLocalizedString()\"\n    match_kinds: string\n    excluded: '.*Tests\/'<\/pre>\n<h2>Comparing to Bool<\/h2>\n<p>I&#8217;m not sure why anyone would do such a thing, but some people write <code>if (isHidden == true)<\/code> instead of simply <code>if isHidden<\/code>. I don&#8217;t even know in which language that would make sense, but definitely not Swift. Therefore:<\/p>\n<pre class=\"lang:yml decode:true\">custom_rules:\n  already_true:\n    regex: \"== true\"\n    message: \"Don't compare to true, just use the bool value.\"\n  already_bool:\n    regex: \"== false\"\n    message: \"Don't compare to false, just use !value.\"\n<\/pre>\n<h2>Commented-out Code<\/h2>\n<p>If you use source-control, there is no reason to leave old commented-out code &#8211; you can always find it in your repository. Just clean it up.<\/p>\n<pre>custom_rules:\n  commented_code:\n    regex: '(?&amp;lt;!:|\\\/)\\\/\\\/\\h*[a-z.](?!wiftlint)'\n    message: \"Comment starting with lowercase letter - did you forget to delete old code?\"\n  multiline_commented_code:\n    regex: '^\\s*[a-z]'\n    match_kinds: comment\n    message: \"Comment starting with lowercase letter - did you forget to delete old code?\"\n<\/pre>\n<h2>Strong self capture in block<\/h2>\n<p>So easy to forget <code>[weak self]<\/code> and create a memory leak, especially when moving code around. These rules will catch many strong self captures, though not 100% of them.<\/p>\n<pre>custom_rules:\n  self_capture_in_async:\n    name: \"Self Capture In async\"\n    message: \"Use [weak self] to avoid retain cycles in async blocks\"\n    regex: 'async\\s*\\{[^\\}]*self\\.'\n  self_capture_in_async_after:\n    name: \"Self Capture In asyncAfter\"\n    message: \"Use [weak self] to avoid retain cycles in asyncAfter blocks\"\n    regex: 'asyncAfter[^\\{]*\\{[^\\}]*self\\.'\n  self_capture_in_block:\n    name: \"Self Capture In Block\"\n    message: \"Self captured in block\"\n    regex: '(?&lt;!defer )\\{ self\\.'\n    match_kinds:\n      - identifier\n      - keyword<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>SwiftLint and SwiftFormat together cover 80% of recurring code-review comments. They&#8217;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 &hellip; <a href=\"https:\/\/ootips.org\/yonat\/useful-custom-rules-for-swiftlint\/\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","enabled":false},"version":2}},"categories":[1],"tags":[17],"class_list":["post-288","post","type-post","status-publish","format-standard","hentry","category-uncategorized","tag-swift"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/p2aEUP-4E","_links":{"self":[{"href":"https:\/\/ootips.org\/yonat\/wp-json\/wp\/v2\/posts\/288","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/ootips.org\/yonat\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/ootips.org\/yonat\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/ootips.org\/yonat\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/ootips.org\/yonat\/wp-json\/wp\/v2\/comments?post=288"}],"version-history":[{"count":29,"href":"https:\/\/ootips.org\/yonat\/wp-json\/wp\/v2\/posts\/288\/revisions"}],"predecessor-version":[{"id":461,"href":"https:\/\/ootips.org\/yonat\/wp-json\/wp\/v2\/posts\/288\/revisions\/461"}],"wp:attachment":[{"href":"https:\/\/ootips.org\/yonat\/wp-json\/wp\/v2\/media?parent=288"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/ootips.org\/yonat\/wp-json\/wp\/v2\/categories?post=288"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/ootips.org\/yonat\/wp-json\/wp\/v2\/tags?post=288"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}