SwiftFormat is a code formatting tool for Swift. It applies a set of rules to Swift source files, transforming them to follow consistent style conventions.
Sources/Rules/- Individual formatting rules (one file per rule)Tests/Rules/- Test cases for rulesSources/Formatter.swift- Core formatter class with token manipulation APIsSources/ParsingHelpers.swift- Parsing helpers for Swift grammar (types, declarations, expressions, etc.)Sources/FormattingHelpers.swift- Higher-level formatting utilitiesSources/Options.swift- Options for configuring the behavior of individual rulesSources/OptionDescriptor.swift- Command line flag configuration for rule options
All changes and pull requests should target the develop branch, not main.
# Build the project
swift build
# Run all tests (on macOS)
swift test
# Run all tests (on Linux)
# --enable-test-discovery is required because LinuxMain.swift (kept for Mint
# compatibility) has an empty test list that overrides automatic discovery.
swift test --enable-test-discovery
# Test a specific rule (works on both macOS and Linux)
./Scripts/test_rule.sh <ruleName>
# Format the codebase (run after making changes)
# ./Scripts/test_rule.sh runs this automatically
./format.shRead the helper files thoroughly. Before writing any rule, familiarize yourself with the existing helpers:
Sources/Formatter.swift- Token manipulation APIsSources/ParsingHelpers.swift- Parsing helpers for types, declarations, expressions, properties, functions, etc.Sources/FormattingHelpers.swift- Higher-level formatting utilitiesSources/Declarations.swift- Helpers related to individual declarations
You MUST review the APIs available in these files using:
$ rg "^ (func|var) " Sources/Formatter.swift Sources/FormattingHelpers.swift Sources/ParsingHelpers.swift Sources/Declaration.swiftMany rules can be implemented using existing helpers. Before writing custom token parsing code, verify no existing helper does what you need.
Create a new file in Sources/Rules/ named after your rule (e.g., MyRule.swift). Rules are defined as static properties on FormatRule:
//
// MyRule.swift
// SwiftFormat
//
import Foundation
public extension FormatRule {
/// Brief description of what the rule does
static let myRule = FormatRule(
help: "Description shown in --help output."
) { formatter in
// Rule implementation goes here. For example:
formatter.forEach(.keyword("let")) { i, _ in
// Process each occurrence
}
} examples: {
"""
```diff
- before
+ after
```
"""
}
}Rule tests are implemented in Tests/Rules/MyRuleTests.swift.
- File author names: when creating new files, always use the name of the actual user / human author, and the current date. NEVER write a file header like "Created by Claude Code" or "Created by GitHub Copilot". Prefer the user's full name as opposed to just their username.
- Minimal changes only. Only modify tokens when an actual change is needed. Any modification triggers a lint error in
--lintmode. - Preserve comments. Prefer preserving code as-is if updating would require removing comments.
- Keep it simple. Write as little code as possible. If a change dramatically increases complexity, consider asking if it should be de-scoped.
- Define local helpers in extensions on
Formatterat the bottom of the rule file. Mark theminternalfor discoverability. Move helpers used by multiple rules toParsingHelpers.swift. Parsing code should almost always be factored out into a rule-specific helper (or later a shared helper if necessary) rather than being implemented directly in the rule implementation closure.
Always use Formatter APIs. Never manipulate token arrays directly:
// ✓ Good - use Formatter APIs
formatter.insert(.space(" "), at: index)
formatter.removeToken(at: index)
formatter.replaceToken(at: index, with: .keyword("let"))
// ✗ Bad - array manipulation
var tokens = Array(formatter.tokens[start...end])
tokens.append(.space(" "))
formatter.replaceTokens(in: start...end, with: tokens)Never use raw index loops. Always traverse tokens using helpers:
// ✓ Good - use traversal helpers
formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i)
formatter.index(of: .keyword("func"), before: i)
formatter.endOfLine(at: i)
// ✗ Bad - raw index manipulation
while i < formatter.tokens.count { i += 1 }
for i in 0..<formatter.tokens.count { ... }Use the testFormatting helper defined in XCTestCase extensions:
func testMyRule() {
let input = """
// input code
"""
let output = """
// expected output
"""
testFormatting(for: input, output, rule: .myRule)
}- Create several test scenarios covering different cases, but don't exhaustively test every configuration.
- Use
testFormatting(for: input, [output], rules: [.myRule, .otherRule])to test multiple rules together. - Prefer formatting
inputandoutputcode to match the behavior of other rules, instead of excluding other rules withexclude:. Only useexclude:if the other rule being applied directly conflicts with what the test case is trying to test. - Do not use
// MARKcomments in tests. - Always use multi-line string literals (""") for input and output code.
To debug a rule, run the existing tests or create new test cases. NEVER try to directly run SwiftFormat on a file via the command line.
Use print debugging as necessary to gather more context. Run individual test cases using:
# On macOS:
swift test --filter <TestClassName>.<testMethodName>
# On Linux (--enable-test-discovery required):
swift test --enable-test-discovery --filter <TestClassName>.<testMethodName>- Run the rule tests:
./Scripts/test_rule.sh <ruleName> - Run the full test suite:
swift test(orswift test --enable-test-discoveryon Linux) - [VERY IMPORTANT] Review your code - ensure it follows all best practices above
- [VERY IMPORTANT] Simplify further - look for functionality that could be removed to reduce complexity
Note: Do not modify Rules.md directly. It's auto-generated when running the test suite. MetadataTests may fail after adding a new rule; re-run after metadata regenerates.
@CONTRIBUTING.md