Install
openclaw skills install swiftlintSwift linting and style enforcement via CLI
openclaw skills install swiftlintEnforce Swift style and conventions with static analysis. Lint entire projects, autocorrect fixable violations, manage rules, and integrate with Xcode and CI — all from the CLI.
swiftlint version
If not installed:
brew install swiftlint
Or via Mint:
mint install realm/SwiftLint
Or as a Swift Package Manager plugin (add to Package.swift):
.package(url: "https://github.com/realm/SwiftLint.git", from: "0.57.0")
Then run:
swift package plugin swiftlint
swiftlint
This recursively lints all .swift files from the current directory.
swiftlint lint --path Sources/
swiftlint lint --path Sources/App/ViewModel.swift
cat MyFile.swift | swiftlint lint --use-stdin --quiet
Agent guidance: When a user says "check my code style" or "lint my Swift code," run
swiftlintfrom the project root. If they point to a specific file or folder, use--path.
SwiftLint can automatically fix certain violations.
swiftlint --fix
swiftlint --fix --path Sources/
swiftlint --fix --path Sources/App/ViewModel.swift
Lint first to see violations, then fix:
swiftlint lint --path Sources/ && swiftlint --fix --path Sources/
Agent guidance: Always lint before autocorrecting so the user sees what will change. Some violations are not autocorrectable — report those separately after fixing.
swiftlint
Output: Sources/App.swift:12:1: warning: Line Length Violation: ...
swiftlint lint --reporter json
swiftlint lint --reporter csv
swiftlint lint --reporter checkstyle
swiftlint lint --reporter github-actions-logging
swiftlint lint --reporter xcode-summary
swiftlint lint --reporter sonarqube
swiftlint lint --reporter markdown
swiftlint lint --reporter json > swiftlint-results.json
| Reporter | Format | Best For |
|---|---|---|
xcode | Xcode-compatible (default) | Local development |
json | JSON array | Programmatic processing |
csv | CSV | Spreadsheet analysis |
checkstyle | XML | Jenkins, CI tools |
codeclimate | Code Climate JSON | Code Climate integration |
github-actions-logging | GitHub annotations | GitHub Actions CI |
sonarqube | SonarQube JSON | SonarQube integration |
markdown | Markdown table | PR comments |
emoji | Emoji-decorated | Fun terminal output |
html | HTML report | Browser viewing |
junit | JUnit XML | Test reporting tools |
xcode-summary | Plist | Xcode build summaries |
Agent guidance: Use
--reporter jsonwhen you need to parse results programmatically. Use--reporter github-actions-loggingon GitHub Actions to get inline annotations on PRs.
swiftlint rules
swiftlint rules | grep "force_cast"
swiftlint rules force_cast
Rules fall into categories:
Enabled by Default (Common)
| Rule | What It Catches |
|---|---|
line_length | Lines exceeding max length (default 120 warning, 200 error) |
trailing_whitespace | Whitespace at end of lines |
trailing_newline | Missing or extra trailing newlines |
opening_brace | Opening brace placement |
closing_brace | Closing brace placement |
colon | Colon spacing (e.g., let x : Int → let x: Int) |
comma | Comma spacing |
force_cast | Use of as! |
force_try | Use of try! |
force_unwrapping | Use of ! on optionals (opt-in) |
type_body_length | Type bodies exceeding max lines |
function_body_length | Function bodies exceeding max lines |
file_length | Files exceeding max lines |
cyclomatic_complexity | High cyclomatic complexity |
nesting | Deep nesting levels |
identifier_name | Naming convention violations |
type_name | Type naming convention violations |
unused_import | Unused import statements (opt-in) |
unused_declaration | Unused declarations (opt-in) |
vertical_whitespace | Excessive blank lines |
todo | TODO/FIXME comments as warnings |
mark | Improper MARK comment format |
void_return | Explicit -> Void instead of -> () |
syntactic_sugar | Prefer [Int] over Array<Int> |
redundant_optional_initialization | var x: Int? = nil (nil is default) |
redundant_string_enum_value | Enum case name matches raw value |
Opt-In (Must Be Explicitly Enabled)
| Rule | What It Catches |
|---|---|
explicit_type_interface | Missing explicit type annotations |
missing_docs | Missing documentation comments |
multiline_arguments | Multiline function call formatting |
multiline_parameters | Multiline function param formatting |
vertical_parameter_alignment | Parameter alignment in declarations |
sorted_imports | Unsorted import statements |
file_header | Missing or incorrect file headers |
accessibility_label_for_image | Images without accessibility labels |
accessibility_trait_for_button | Buttons without accessibility traits |
strict_fileprivate | Prefer private over fileprivate |
prohibited_interface_builder | Storyboard/XIB usage |
no_magic_numbers | Magic numbers in code |
prefer_self_in_static_references | Self over explicit type name in static context |
balanced_xctest_lifecycle | setUp without tearDown |
test_case_accessibility | Test methods not marked correctly |
Agent guidance: When setting up SwiftLint for a new project, start with defaults and only add opt-in rules the user specifically requests. Don't overwhelm with every possible rule.
SwiftLint reads .swiftlint.yml from the current directory (or parent directories).
There's no built-in generator, but here's a minimal config:
# .swiftlint.yml
# Paths to include (default: all Swift files)
included:
- Sources
- Tests
# Paths to exclude
excluded:
- Pods
- DerivedData
- .build
- Packages
# Disable specific rules
disabled_rules:
- todo
- trailing_whitespace
# Enable opt-in rules
opt_in_rules:
- sorted_imports
- unused_import
- missing_docs
# Configure specific rules
line_length:
warning: 120
error: 200
ignores_comments: true
ignores_urls: true
type_body_length:
warning: 300
error: 500
file_length:
warning: 500
error: 1000
function_body_length:
warning: 50
error: 100
identifier_name:
min_length:
warning: 2
error: 1
max_length:
warning: 50
error: 60
excluded:
- id
- x
- y
- i
- j
- ok
type_name:
min_length:
warning: 3
error: 0
max_length:
warning: 50
error: 60
cyclomatic_complexity:
warning: 10
error: 20
nesting:
type_level:
warning: 2
function_level:
warning: 3
swiftlint lint --config path/to/.swiftlint.yml
# Feature/.swiftlint.yml — inherits parent config and overrides
child_config: ../.swiftlint.yml
disabled_rules:
- force_cast
# .swiftlint.yml
parent_config: shared/.swiftlint-base.yml
parent_config: https://raw.githubusercontent.com/org/repo/main/.swiftlint.yml
Agent guidance: When a project already has
.swiftlint.yml, always read it before suggesting changes. Modify the existing config rather than creating a new one.
let value = dict["key"] as! String // swiftlint:disable:this force_cast
// swiftlint:disable force_cast
let a = x as! String
let b = y as! Int
// swiftlint:enable force_cast
// swiftlint:disable all
// Legacy code that can't be refactored yet
// swiftlint:disable:enable all
// swiftlint:disable:next force_cast
let value = dict["key"] as! String
let value = dict["key"] as! String
// swiftlint:disable:previous force_cast
Agent guidance: Prefer targeted disables (
disable:this,disable:next) over broad block disables. Never suggestdisable allunless the user is dealing with generated code or legacy code they explicitly don't want to lint.
Add a Run Script Phase in Xcode (Build Phases):
if command -v swiftlint >/dev/null 2>&1; then
swiftlint
else
echo "warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint"
fi
if command -v swiftlint >/dev/null 2>&1; then
swiftlint --fix && swiftlint
fi
If SwiftLint is added as a package dependency:
swift package plugin swiftlint
Or in Xcode: right-click the project → "SwiftLintBuildToolPlugin".
Agent guidance: For modern projects (Xcode 15+), prefer the SPM plugin over a build phase script — it pins the SwiftLint version to the project and doesn't require a local install.
- name: SwiftLint
run: |
brew install swiftlint
swiftlint lint --reporter github-actions-logging --strict
With only changed files:
- name: SwiftLint Changed Files
run: |
git diff --name-only --diff-filter=d origin/main...HEAD -- '*.swift' | \
xargs -I{} swiftlint lint --path {} --reporter github-actions-logging --strict
- script:
inputs:
- content: |
brew install swiftlint
swiftlint lint --strict
swiftlint lint --reporter checkstyle > swiftlint-checkstyle.xml
Then use the Checkstyle plugin to parse swiftlint-checkstyle.xml.
swiftlint lint --reporter json | python3 -c "
import json, sys, collections
data = json.load(sys.stdin)
counts = collections.Counter(v['rule_id'] for v in data)
for rule, count in counts.most_common():
print(f'{count:>5} {rule}')
"
swiftlint lint --strict 2>&1 | grep "error:"
swiftlint lint --strict
This makes SwiftLint return a non-zero exit code for any violation (not just errors).
swiftlint lint --quiet
Suppresses warnings from output, only shows errors.
swiftlint lint --enable-rules sorted_imports,unused_import
swiftlint lint --disable-rules todo,trailing_whitespace
Define custom regex-based rules in .swiftlint.yml:
custom_rules:
no_print_statements:
name: "No Print Statements"
regex: "\\bprint\\s*\\("
message: "Use os_log or Logger instead of print()"
severity: warning
match_kinds:
- identifier
no_hardcoded_strings:
name: "No Hardcoded Strings in Views"
regex: "Text\\(\"[^\"]+\"\\)"
message: "Use LocalizedStringKey or String(localized:) for user-facing text"
severity: warning
included: ".*View\\.swift"
no_force_unwrap_iboutlet:
name: "No Force Unwrap IBOutlet"
regex: "@IBOutlet\\s+(weak\\s+)?var\\s+\\w+:\\s+\\w+!"
message: "Use optional IBOutlets to avoid crashes"
severity: error
accessibility_identifier_required:
name: "Accessibility Identifier"
regex: "\\.accessibilityIdentifier\\("
message: "Good — accessibilityIdentifier found"
severity: warning
match_kinds:
- identifier
prefer_logger_over_print:
name: "Prefer Logger"
regex: "\\bNSLog\\s*\\("
message: "Use Logger (os.Logger) instead of NSLog"
severity: warning
| Kind | What It Matches |
|---|---|
identifier | Variable/function names |
string | String literals |
comment | Comments |
keyword | Swift keywords |
typeidentifier | Type names |
number | Numeric literals |
parameter | Function parameters |
argument | Function arguments |
Agent guidance: Custom rules are powerful but regex-based — they can produce false positives. Always test custom rules on the codebase before suggesting them as permanent additions.
| Flag | Purpose |
|---|---|
--path <path> | Lint specific file or directory |
--config <path> | Use custom config file |
--reporter <name> | Output format (json, csv, etc.) |
--strict | Treat warnings as errors |
--quiet | Only show errors in output |
--fix | Autocorrect fixable violations |
--enable-rules <rules> | Enable specific rules (comma-separated) |
--disable-rules <rules> | Disable specific rules (comma-separated) |
--use-stdin | Read Swift from stdin |
--force-exclude | Exclude files even if explicitly passed |
--cache-path <path> | Custom cache directory |
--no-cache | Disable caching |
--use-alternative-excluding | Use alternative file-excluding method |
--in-process-sourcekit | Use in-process SourceKit |
--compiler-log-path | Path to xcodebuild log for analyzer rules |
--progress | Show progress bar |
# 1. Install
brew install swiftlint
# 2. Run initial lint to see baseline
swiftlint lint --path Sources/ --reporter json > baseline.json
# 3. Create config based on results
cat > .swiftlint.yml << 'EOF'
included:
- Sources
- Tests
excluded:
- Pods
- DerivedData
- .build
disabled_rules:
- todo
opt_in_rules:
- sorted_imports
- unused_import
line_length:
warning: 120
error: 200
ignores_urls: true
EOF
# 4. Fix autocorrectable issues
swiftlint --fix --path Sources/
# 5. Re-lint to see remaining issues
swiftlint
# Autocorrect what we can
swiftlint --fix
# Check what remains
swiftlint lint --strict
git diff --name-only --diff-filter=d origin/main...HEAD -- '*.swift' | \
xargs -I{} swiftlint lint --path {} --strict
# Current branch count
CURRENT=$(swiftlint lint --quiet 2>&1 | wc -l | tr -d ' ')
# Main branch count
git stash
git checkout main
MAIN=$(swiftlint lint --quiet 2>&1 | wc -l | tr -d ' ')
git checkout -
git stash pop
echo "main: $MAIN violations, current: $CURRENT violations"
| Error | Solution |
|---|---|
No lintable files found | Check included paths in config or run from project root |
Invalid configuration | Validate YAML syntax in .swiftlint.yml |
SourceKit not found | Run sudo xcode-select -s /Applications/Xcode.app |
Rule not found | Check rule name with swiftlint rules, may be opt-in |
Slow linting | Add excluded paths for Pods/DerivedData, use --cache-path |
Pods/, DerivedData/, .build/, and Packages/ in config--cache-path to persist cache across CI runs--no-cache only when debugging unexpected resultsWhen a user asks to "clean up" or "fix style" in Swift code, run
swiftlint --fixfirst, thenswiftlint lintto report remaining issues.
Before adding SwiftLint to an existing project, run
swiftlint lint --reporter jsonto assess the violation count. If there are hundreds of violations, suggest a phased approach: fix autocorrectable ones first, then tackle the rest by category.
Always check for an existing
.swiftlint.ymlbefore creating one. Read it to understand the team's style preferences.
For projects targeting accessibility (which should be all of them), suggest enabling
accessibility_label_for_imageandaccessibility_trait_for_buttonopt-in rules.
When the user's project uses SwiftUI, suggest a custom rule to catch hardcoded strings in
Text()views — all user-facing strings should useString(localized:)for localization support.
The
--strictflag is essential for CI — without it, SwiftLint exits 0 even with warnings, which means your CI pipeline won't catch style regressions.