Introduction
We have added linting for the Kotlin Android application, but that is only half of the KMM architecture. The other half, Native Swift (Swift), is missing out on all the benefits of Danger.
In this article, we will explore static code analysis on iOS, covering local linting, integrating linting into a Continuous Integration (CI) flow using GitHub Actions, and finally, generating and publishing a summary report for each Pull Request.
Why lint
💡 TL;DR: Lint is an efficient way to ensure code quality.
Read more about why linting in the first part “Linting KMM Android Application with Danger”.
Lint with SwiftLint
SwiftLint is a Swift linter tool for Swift that enforces iOS coding standards automatically.
There are multiple ways to install SwiftLint. The recommended way is via CocoaPods because:
- KMM project uses
CocoaPodsto connect the Native application to the Kotlin modules. - Keep
SwiftLintdependent on the project.
Add the following pod to the Podfile, then run pod install in the iosApp directory.
target 'iosApp' do
# Other pods
# ...
pod 'SwiftLint'
# ...
end
SwiftLint is run via Pods/SwiftLint/SwiftLint inside the iosApp folder or iosApp/Pods/SwiftLint/SwiftLint from the root folder.
Configure SwiftLint Rules
SwiftLint allows rule configuration via .SwiftLint.yml. Here is an example configuration.
excluded:
- Pods
- Derived
- DerivedData
💡
SwiftLintcan automatically display linting warnings and errors in Xcode during code compilation, helping developers fix issues during development. Setup instructions are available on the project’s GitHub page.
Now that we’ve covered SwiftLint, let’s explore how Danger can automate our linting workflow.
SwiftLint with Danger
We will create a new Gemfile and Dangerfile inside the iosApp directory, separated from the KMM modules.
Add the danger and CocoaPods gems to the iosApp/Gemfile.
# Gemfile
source "https://rubygems.org"
gem "CocoaPods"
gem "danger"
gem "danger-SwiftLint"
Add SwiftLint plugin to iosApp/Dangerfile. Danger will execute SwiftLint from the plugin.
# Dangerfile
# SwiftLint
SwiftLint.binary_path = 'iosApp/Pods/SwiftLint/SwiftLint'
SwiftLint.config_file = 'iosApp/.SwiftLint.yml'
SwiftLint.max_num_violations = 20
SwiftLint.lint_files(
inline_mode: true,
fail_on_error: true, # Stop CI when fail
additional_SwiftLint_args: '--strict'
)
Create a CI/CD workflow that performs actions within the iosApp subdirectory.
name: iOS Review pull request
on:
pull_request
defaults:
run:
working-directory: iosApp
jobs:
review_pull_request:
name: Review pull request
runs-on: macos-latest
timeout-minutes: 30
steps:
- name: Set up JAVA 11
uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: '11'
- name: Checkout source code
uses: actions/checkout@v4
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.4'
bundler-cache: true
working-directory: 'iosApp'
- name: Install gems
run: |
bundle install
Danger will now run SwiftLint and comment on the files in a pull request.
Finally, add Danger execution to the end of CI/CD workflow such as this GitHub Actions example.
...
- name: Run iOS Danger
uses: MeilCli/danger-action@v5
with:
plugins_file: 'iosApp/Gemfile'
install_path: 'iosApp/vendor/bundle'
danger_file: 'iosApp/Dangerfile'
danger_id: 'danger-pr'
env:
DANGER_GITHUB_API_TOKEN: $
Linting with SwiftFormat
SwiftFormat is a code reformatter for Swift. It supports most of SwiftLint’s format and a few extra. SwiftFormat automatically formats your Swift code before pushing, ensuring it adheres to project conventions.
With Danger, SwiftFormat can be used for linting Swift code on the CI/CD, to check for some conventions that SwiftLint is missing.
Adding SwiftFormat
Add SwiftFormat as a CocoaPods dependency to the Podfile
target 'iosApp' do
# Other pods
# ...
pod 'SwiftFormat/CLI'
# ...
end
💡 The SwiftFormat Github page shows multiple ways to install SwiftFormat. Using CocoaPods is the preferred way because Danger will require the path for SwiftFormat later on.
SwiftFormat can now be executed by running iosApp/Pods/SwiftFormat/CommandLineTool/swiftformat.
SwiftFormat can be configured with iosApp/.swiftformat file. Here is an example of .swiftformat file.
# file options
--exclude Pods, Generated, **/*.generated.swift
# rules
--disable fileHeader
--disable initCoderUnavailable
SwiftFormat with Danger
Add the SwiftFormat Danger plugin to the iosApp/Gemfile.
# Gemfile
source "https://rubygems.org"
gem "CocoaPods"
gem "danger"
gem 'danger-swiftformat'
The last step is to add SwiftFormat to iosApp/Dangerfile.
# SwiftFormat
swiftformat.binary_path = 'iosApp/Pods/SwiftFormat/CommandLineTool/swiftformat'
swiftformat.exclude = %w(Pods/** **/*generated.swift)
swiftformat.check_format
Make sure to bundle exec danger or use the Danger action in the CI/CD workflow.
Conclusion
Linting and formatting are essential for maintaining a clean, high-quality codebase in KMM projects. By integrating SwiftLint, SwiftFormat, and Danger, you can automate these checks, streamline code reviews, and prevent technical debt from creeping into your iOS codebase.
To learn more about code coverage with Danger, make sure to read the upcoming KMM iOS Code Coverage blog post, or read about KMM Android lint and KMM Android code coverage.