Skip to content

Latest commit

 

History

History
176 lines (130 loc) · 6.56 KB

File metadata and controls

176 lines (130 loc) · 6.56 KB

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

Branching

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

Building and Testing

# 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:

$ 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:

//
//  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:

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

Writing Tests

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 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:

# 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