DangerSwift is a plugin for Danger tailored to Swift projects. It helps automate code review tasks, ensuring your pull requests meet your team’s standards. With DangerSwift, you can enforce linting rules, track test coverage, detect large file additions, and much more — all integrated into your CI/CD pipeline.
Key Features:
- Written in Swift, with full support for the language's ecosystem.
- Flexible DSL for writing custom rules.
- Works seamlessly with popular CI services like GitHub Actions, Bitrise, and more.
- Actively maintained and community-driven.
Get started with automating your code reviews today!
1. See this example: Example :
name: CI and Danger
on:
pull_request:
branches:
- main
- develop
jobs:
ci:
name: CI Build and Tests
permissions: write-all
runs-on: macos-14
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Set up Xcode
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '16.0'
- name: Install CocoaPods
run: |
cd Example
pod install
- name: Setup Danger
run: |
git clone https://github.com/DebugSwift/DangerSwift && rm -rf DangerSwift/.git Readme.md
mv DangerSwift/* .
- name: Test Stage
run: |
cd Example
bundle install
bundle exec fastlane test
- name: Danger Stage
run: |
brew install danger/tap/danger-js
swift build
swift run danger-swift ci --verbose
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DANGER_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
included:
- DebugSwift <Optional: Your package name>
excluded:
- Tests
analyzer_rules:
- unused_declaration
- unused_import
opt_in_rules:
- all
disabled_rules:
- anonymous_argument_in_multiline_closure
- anyobject_protocol
- closure_body_length
- conditional_returns_on_newline
- convenience_type
- discouraged_optional_collection
- explicit_acl
- explicit_enum_raw_value
- explicit_top_level_acl
- explicit_type_interface
- file_types_order
- final_test_case
- force_unwrapping
- function_default_parameter_at_end
- implicit_return
- implicitly_unwrapped_optional
- indentation_width
- inert_defer
- missing_docs
- multiline_arguments
- multiline_arguments_brackets
- multiline_function_chains
- multiline_literal_brackets
- multiline_parameters
- multiline_parameters_brackets
- no_extension_access_modifier
- no_fallthrough_only
- no_grouping_extension
- no_magic_numbers
- one_declaration_per_file
- prefer_nimble
- prefer_self_in_static_references
- prefixed_toplevel_constant
- redundant_self_in_closure
- required_deinit
- self_binding
- static_over_final_class
- shorthand_argument
- sorted_enum_cases
- strict_fileprivate
- switch_case_on_newline
- todo
- trailing_closure
- type_contents_order
- unused_capture_list
- vertical_whitespace_between_cases
attributes:
always_on_line_above:
- "@ConfigurationElement"
- "@OptionGroup"
- "@RuleConfigurationDescriptionBuilder"
identifier_name:
excluded:
- id
large_tuple: 3
number_separator:
minimum_length: 5
file_name:
excluded:
- Exports.swift
- GeneratedTests.swift
- RuleConfigurationMacros.swift
- SwiftSyntax+SwiftLint.swift
- TestHelpers.swift
unneeded_override:
affect_initializers: true
balanced_xctest_lifecycle: &unit_test_configuration
test_parent_classes:
- SwiftLintTestCase
- XCTestCase
empty_xctest_method: *unit_test_configuration
single_test_class: *unit_test_configuration
function_body_length: 60
type_body_length: 400
// MARK: Imports
import Danger
import DangerSwiftCoverage
import DangerXCodeSummary
import Foundation
// MARK: Validate
Validator.shared.validate()
// MARK: Lint
SwiftLint.lint(configFile: ".swiftlint.yml")
// MARK: Validation rules
internal class Validator {
// MARK: Lifecycle
// Private initializer and shared instance for Validator.
private init() {}
internal static let shared = Validator()
private var danger = Danger()
// MARK: Properties
// Properties related to PR details and changes.
private lazy var additions = danger.github.pullRequest.additions!
private lazy var deletions = danger.github.pullRequest.deletions!
private lazy var changedFiles = danger.github.pullRequest.changedFiles!
private lazy var modified = danger.git.modifiedFiles
private lazy var editedFiles = modified + danger.git.createdFiles
private lazy var prTitle = danger.github.pullRequest.title
private lazy var branchHeadName = danger.github.pullRequest.head.ref
private lazy var branchBaseName = danger.github.pullRequest.base.ref
// Methods
// Methods for various validation checks.
internal func validate() {
checkSize()
checkDescription()
checkUnitTest()
checkTitle()
checkAssignee()
checkModifiedFiles()
checkFails()
logResume()
}
}
internal class DescriptionValidator {
// MARK: Lifecycle
// Private initializer and shared instance for DescriptionValidator.
private init() {}
internal static let shared = DescriptionValidator()
private var danger = Danger()
// MARK: Properties
// Property to store the PR body.
private lazy var body = danger.github.pullRequest.body ?? ""
// Methods
// Method to validate PR description.
internal func validate() {
let message = "PR does not have a description. You must provide a description of the changes made."
guard !body.isEmpty else {
fail(message)
return
}
}
}
internal class UnitTestValidator {
// MARK: Lifecycle
// Private initializer and shared instance for UnitTestValidator.
private init() {}
internal static let shared = UnitTestValidator()
private var danger = Danger()
// Methods
// Methods for unit test validation.
internal func validate() {
checkUnitTestSummary()
checkUnitTestCoverage()
}
}
// MARK: Validator Methods
// Extension with methods for Validator class.
fileprivate extension Validator {
func checkSize() {
if (additions + deletions) > ValidationRules.bigPRThreshold {
let message =
"""
The size of the PR seems relatively large. \
If possible, in the future if the PR contains multiple changes, split each into a separate PR. \
This helps in faster and easier review.
"""
warn(message)
}
}
func checkDescription() {
DescriptionValidator.shared.validate()
}
func checkUnitTest() {
UnitTestValidator.shared.validate()
}
func checkTitle() {
let result = prTitle.range(
of: #"\[[A-zÀ-ú0-9 ]*\][A-zÀ-ú0-9- ]+"#,
options: .regularExpression
) != nil
if !result {
let message = "The PR title should be: [<i>Feature or Flow</i>] <i>What flow was done</i>"
warn(message)
}
}
func checkAssignee() {
if danger.github.pullRequest.assignee == nil {
warn("Please assign yourself to the PR.")
}
}
func checkModifiedFiles() {
if changedFiles > ValidationRules.maxChangedFiles {
let message =
"""
PR contains too many changed files. If possible, next time try to split into smaller features.
"""
warn(message)
}
}
func checkFails() {
if !danger.fails.isEmpty {
_ = danger.utils.exec("touch Danger-has-fails.swift")
}
}
func logResume() {
let overview =
"""
The PR added \(additions) and removed \(deletions) lines. \(changedFiles) file(s) changed.
"""
// TODO: - Add PR documentation link
let seeOurDocumentation =
"""
Documentation: \
<a href=''> \
Link</a>
"""
// message(seeOurDocumentation)
message(overview)
}
}
// MARK: Constants
// Constants related to validation rules.
private enum ValidationRules {
static let maxChangedFiles = 20
static let bigPRThreshold = 3000
}
// MARK: Extensions
// Extension with additional file-related methods.
fileprivate extension Danger.File {
var isInSources: Bool { hasPrefix("Sources/") }
var isInTests: Bool { hasPrefix("Tests/") }
var isSourceFile: Bool {
hasSuffix(".swift") || hasSuffix(".h") || hasSuffix(".m")
}
var isSwiftPackageDefintion: Bool {
hasPrefix("Package") && hasSuffix(".swift")
}
var isDangerfile: Bool {
self == "Dangerfile.swift"
}
}
// MARK: UnitTestValidator Methods
// Extension with methods for UnitTestValidator class.
fileprivate extension UnitTestValidator {
func checkUnitTestSummary() {
let file = "build/reports/errors.json"
if FileManager.default.fileExists(atPath: file) {
let summary = XCodeSummary(filePath: file) { result in
result.category != .warning
}
summary.report()
}
}
func checkUnitTestCoverage() {
Coverage.xcodeBuildCoverage(
.xcresultBundle("Example/fastlane/test_output/Example.xcresult"),
minimumCoverage: 70,
excludedTargets: ["DangerSwiftCoverageTests.xctest"]
)
}
}
We welcome contributions to improve this project! To contribute, please follow these steps:
- Fork the repository.
- Create a new branch for your feature or bug fix.
- Make your changes and commit them.
- Push your changes to your forked repository.
- Open a pull request to the main repository.
Please ensure that your code follows the project's coding conventions and includes appropriate tests.
This project is licensed under the MIT License. You can find the full license text in the LICENSE file.