Enforce 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 swiftlint from 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 json when you need to parse results programmatically. Use --reporter github-actions-logging on 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 |
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 suggest disable all unless 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 | Lint specific file or directory |
--config | Use custom config file |
--reporter | Output format (json, csv, etc.) |
--strict | Treat warnings as errors |
--quiet | Only show errors in output |
--fix | Autocorrect fixable violations |
--enable-rules | Enable specific rules (comma-separated) |
--disable-rules | Disable specific rules (comma-separated) |
--use-stdin | Read Swift from stdin |
--force-exclude | Exclude files even if explicitly passed |
--cache-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 results> When a user asks to "clean up" or "fix style" in Swift code, run swiftlint --fix first, then swiftlint lint to report remaining issues.
> Before adding SwiftLint to an existing project, run swiftlint lint --reporter json to 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.yml before 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_image and accessibility_trait_for_button opt-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 use String(localized:) for localization support.
> The --strict flag is essential for CI — without it, SwiftLint exits 0 even with warnings, which means your CI pipeline won't catch style regressions.
共 1 个版本