SwiftFormat

GitHub

A command-line tool and Xcode Extension for formatting Swift code

8,879 stars Swift
RAW Rules

CLAUDE.md

# SwiftFormat

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.

## Project Structure

- `Sources/Rules/` - Individual formatting rules (one file per rule)
- `Tests/Rules/` - Test cases for rules
- `Sources/Formatter.swift` - Core formatter class with token manipulation APIs
- `Sources/ParsingHelpers.swift` - Parsing helpers for Swift grammar (types, declarations, expressions, etc.)
- `Sources/FormattingHelpers.swift` - Higher-level formatting utilities
- `Sources/Options.swift` - Options for configuring the behavior of individual rules
- `Sources/OptionDescriptor.swift` - Command line flag configuration for rule options

## PR Workflows

All changes and pull requests should target the `develop` branch, not `main`.

When opening a PR that fixes a GitHub issue, reference the issue in the PR description with "Fixes #100" etc.

## Building and Testing

```sh
# 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.sh
```

## Adding New Rules

### Before You Start

**Read the helper files thoroughly.** Before writing any rule, familiarize yourself with the existing helpers:

- `Sources/Formatter.swift` - Token manipulation APIs
- `Sources/ParsingHelpers.swift` - Parsing helpers for types, declarations, expressions, properties, functions, etc.
- `Sources/FormattingHelpers.swift` - Higher-level formatting utilities
- `Sources/Declarations.swift` - Helpers related to individual declarations

You MUST review the APIs available in these files using:

```bash
$ rg "^    (func|var) " Sources/Formatter.swift Sources/FormattingHelpers.swift Sources/ParsingHelpers.swift Sources/Declaration.swift
```

Many rules can be implemented using existing helpers. Before writing custom token parsing code, verify no existing helper does what you need.

### Rule File Structure

Create a new file in `Sources/Rules/` named after your rule (e.g., `MyRule.swift`). Rules are defined as static properties on `FormatRule`:

```swift
//
//  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`.

### Best Practices

- **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 `--lint` mode.
- **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 `Formatter` at the bottom of the rule file. Mark them `internal` for discoverability. Move helpers used by multiple rules to `ParsingHelpers.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:

```swift
// ✓ 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:

```swift
// ✓ 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 { ... }
```

`formatter.forEach` iteration and `formatter.parseDeclarations()` indices are automatically updated as `formatter.tokens` is modified. It is NOT necessary to pre-compute a list of `formatter.tokens` mutaions to then apply later. ALWAYS just modify the `formatter.tokens` directly, through the available methods on `formatter`. In cases where you do have stored token index values that would be invalidated by changes earlier in the `formatter.tokens` array, the most common strategy is to work from bottom-to-top in the function or file.

### Writing Tests

Use the `testFormatting` helper defined in `XCTestCase` extensions:

```swift
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 `input` and `output` code to match the behavior of other rules, instead of excluding other rules with `exclude:`. Only use `exclude:` if the other rule being applied directly conflicts with what the test case is trying to test.
- Do not use `// MARK` comments in tests.
- Always use multi-line string literals (""") for input and output code.

### Debugging

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:

```sh
# On macOS:
swift test --filter <TestClassName>.<testMethodName>

# On Linux (--enable-test-discovery required):
swift test --enable-test-discovery --filter <TestClassName>.<testMethodName>
```

### After Writing the Rule

1. **Run the rule tests:** `./Scripts/test_rule.sh <ruleName>`
2. **Run the full test suite:** `swift test` (or `swift test --enable-test-discovery` on Linux)
3. **[VERY IMPORTANT] Review your code** - ensure it follows all best practices above
4. **[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.

## See Also

@CONTRIBUTING.md