SwiftFormat Documentation

repository·main·Indexed 27 days ago

https://github.com/nicklockwood/swiftformat

A code library and command-line tool for reformatting Swift code on macOS, Linux, and Windows. SwiftFormat corrects Swift idioms, manages implicit self, and removes redundant parentheses. It supports installation via Homebrew, Mint, Nix, CocoaPods, and Swift Package Manager, and provides integrations for Xcode (extension and build phases), VSCode, GitHub Actions, Danger CI, and Docker.

Tokens
45.6K
Snippets
152
Records
256
Agent score
88%

What's inside SwiftFormat

  1. Overview of the Parsing toy language syntax

    main

    The project implements a simple language with the following syntax rules:

    • Numbers: Integers and floating point (up to Double precision). No negative numbers or exponentials.
    • String Literals: Enclosed in double quotes ("). Supports linebreaks and escaped characters (\", \\).
    • Variables: Must start with a letter, followed by letters or numbers. Keywords let and print are reserved.
    • Expressions: Supports numbers, strings, variables, and the infix + operator (addition for numbers, concatenation for strings).
    • Declarations: let <variable> = <expression>
    • Print: print <expression>
    • Whitespace: Whitespace-agnostic; multiple statements can exist on one line.
    • Comments: Not supported.
  2. Use Templates and the <children/> tag

    main

    Templates allow for a form of inheritance in XML. When a node imports a template using the template attribute, the node's children are appended to the template's children rather than replacing them.

    Rules:

    • The template's root node must be the same class or a superclass of the importing node.
    • To control exactly where the importing node's children are inserted within the template, use the <children/> tag. This tag acts as a placeholder that is replaced by the importing node's content.
    <!-- MyTemplate.xml -->
    <UIView backgroundColor="#fff">
        <UILabel>Shared Heading</UILabel>
        <UIView>
            <children/> <!-- Importing node's children are inserted here -->
        </UIView>
        <UILabel>Shared Footer</UILabel>
    </UIView>
    
    <!-- Usage -->
    <UIView template="MyTemplate.xml">
        <UILabel text="Some unique content"/>
    </UIView>
  3. Evaluate expressions with the Expression REPL

    main

    The Expression REPL is a Mac command-line tool used to evaluate expressions. It is based on AnyExpression, meaning it supports any type representable as a literal in Expression syntax (not just numbers).

    Each line is evaluated independently. You can persist values across lines by defining variables using the syntax identifier = expression.

  4. Optimize Expression Evaluation

    main

    Expressions are optimized by default (e.g., replacing constants with literals or inlining pure functions).

    Performance Guidelines:

    1. Use constants for fixed values: Pass constant values via the constants or arrays arguments instead of the symbols dictionary. This allows the optimizer to inline them.
    2. Use pureSymbols for custom logic: If your custom functions or operators are pure (no side effects, same output for same input), set the pureSymbols option. This allows the optimizer to inline them when all arguments are constant.
    3. Handle complex lookups: For values that are constant but require computation (like hex colors or deep key paths), use the init(pureSymbols:) initializer to decode or look up values.

    Disabling Optimization: If an expression is only evaluated once or twice, you may want to disable optimization to save initialization time using the .noOptimize option.

  5. Install Euclid via CocoaPods, Carthage, or Swift Package Manager

    main

    Euclid is a dynamic framework for 3D geometry manipulation. It requires Xcode 10+ and runs on iOS 10+ or macOS 10.12+.

    CocoaPods Add this to your Podfile:

    pod 'Euclid', '~> 0.1'

    Carthage Add this to your Cartfile:

    gitub "nicklockwood/Euclid" ~> 0.1

    Swift Package Manager Add this to the dependencies: section in your Package.swift:

    .package(url: "https://github.com/nicklockwood/Euclid.git", .upToNextMinor(from: "0.1.0")),
  6. Include SwiftFormat in a Swift Package

    main

    To include the SwiftFormat binary directly in your project using Swift Package Manager, add a .binaryTarget to your Package.swift file. You will need to provide the URL to the .artifactbundle.zip and the appropriate checksum.

    .binaryTarget(
        name: "swiftformat",
        url: "https://github.com/nicklockwood/SwiftFormat/releases/download/0.55.0/swiftformat-macos.artifactbundle.zip",
        checksum: "CHECKSUM"
    ),
  7. Improve error messages using CustomStringConvertible

    main

    When using labelled consumers, Consumer uses the Label type's description to explain what was expected in an error message. To provide user-friendly, localizable descriptions instead of raw enum values, make your Label type conform to CustomStringConvertible and implement the description property.

    Similarly, implement CustomStringConvertible for your custom error types thrown during the transform phase to provide meaningful error messages.

    // For Label types
    enum JSONLabel: String, CustomStringConvertible {
        case string
        case array
        case json
        
        var description: String {
            switch self {
            case .string: return "a string"
            case .array: return "an array"
            case .json: return "a json value"
            }
        }
    }
    
    // For custom transform errors
    enum JSONError: Error, CustomStringConvertible {
        case invalidNumber(String)
        case invalidCodePoint(String)
        
        var description: String {
            switch self {
            case let .invalidNumber(string):
                return "invalid numeric literal '\(string)'"
            case let .invalidCodePoint(string):
                return "invalid unicode code point '\(string)'"
            }
        }
    }
  8. Implement Layout-based Components with LayoutLoading

    main

    To create reusable UI components that use Layout internally, subclass UIView (or UIControl, UIButton, etc.) and conform to the LayoutLoading protocol. This allows you to load XML layouts directly into the view.

    Important Notes:

    • The root view defined in the XML is loaded as a subview of your class and automatically sized to match.
    • Do not attempt to load a view inside itself (e.g., loading MyView.xml inside MyView class), as this causes an infinite loading loop and a runtime error.
    • To support dynamic sizing, override intrinsicContentSize to return the layoutNode?.frame.size and update the frame in layoutSubviews.
    class MyView: UIView, LayoutLoading {
    
        override init(frame: CGRect) {
            super.init(frame: frame)
    
            loadLayout(
                named: "MyView.xml",
                state: ..., // your state object
                constants: ..., // your constants
            )
        }
        
        override func layoutSubviews() {
            super.layoutSubviews()
    
            // Ensure layout is updated after screen rotation, etc
            self.layoutNode?.view.frame = self.bounds
            
            // Update frame to match layout if it has dynamic size
            self.frame.size = self.intrinsicContentSize
        }
    
        public override var intrinsicContentSize: CGSize {
            return layoutNode?.frame.size ?? .zero
        }
    }
  9. Define custom RuntimeType for enums and OptionSets

    main

    To make enum cases or OptionSet values available in Layout expressions, extend the RuntimeType class with @objc static let properties. The property name should match the type name with a lowercase prefix.

    For enums, provide a dictionary mapping string names to the actual enum cases. For OptionSet, do the same with the option values.

    extension RuntimeType {
    
        @objc static let myStructType = RuntimeType(MyStructType.self)
    
        @objc static let nsTextAlignment = RuntimeType([
            "left": .left,
            "right": .right,
            "center": .center,
            "justified": .justified,
            "natural": .natural,
        ] as [String: NSTextAlignment])
    
        @objc static let uiDataDetectorTypes = RuntimeType([
            "phoneNumber": .phoneNumber,
            "link": .link,
            "address": .address,
            "calendarEvent": .calendarEvent,
            "all": .all,
        ] as [String: UIDataDetectorTypes])
    }
  10. Use Typed Labels for safer transformations

    main

    To avoid string-based label matching and the need for default: clauses in switch statements, use a custom enum as the Label type for your Consumer. The label type must conform to Hashable.

    Using an enum provides:

    1. Type Safety: Eliminates error-prone string literals.
    2. Exhaustiveness: The Swift compiler will warn you if you forget to handle a specific label in your transform closure.
    3. Cleaner Code: Removes the need for default: cases in switch statements when all enum cases are covered.
    enum MyLabel: String {
        case integer
    }
    
    let integer: Consumer<MyLabel> = .label(.integer, .flatten(.any([
        // ... consumer definition ...
    ])))
    
    let result = try integer.match("1234").transform { label, values in
        switch label {
        case .integer:
            let string = values[0] as! String
            return Int(string)
        }
    }
  11. Define reusable expressions with Macros

    main

    Macros are reusable expressions defined inside a Layout template. Unlike parameters, macros cannot be set or overridden externally; their value is determined at the point of use within the hierarchy. They are ideal for DRYing up layouts that use relative values like 100% or previous.bottom.

    Best Practice: Use UPPERCASE names for macros to visually distinguish them from constants, parameters, or state variables and to avoid namespace collisions with view properties.

    <UIView>
        <macro name="SPACING" value="20"/>
        <macro name="LEFT" value="SPACING"/>
        <macro name="RIGHT" value="100% - SPACING"/>
        <macro name="TOP" value="previous.bottom + SPACING"/>
        
        <UILabel left="LEFT" right="RIGHT" top="TOP" text="Foo"/>
        <UILabel left="LEFT" right="RIGHT" top="TOP" text="Bar"/>
    </UIView>
  12. Manage Expression Caching

    main

    By default, Expression caches parsed expressions in an unlimited-size cache.

    To clear the cache:

    • Clear everything: Expression.flushCache()
    • Clear a specific expression: Expression.flushCache(for: "foo + bar")

    To avoid caching: If you want fine-grained control or want to avoid the global cache, pre-parse the expression with usingCache: false and then initialize the Expression instance from the parsed result.

    Parsing embedded expressions: You can use a variant of Expression.parse() that accepts a String.UnicodeScalarView.SubSequence and a delimiter to extract and parse an expression from within a larger string.

    // Clear all cached expressions
    Expression.flushCache()
    
    // Clear a specific expression
    Expression.flushCache(for: "foo + bar")
    
    // Parse without caching
    let expressionString = "foo + bar"
    let parsedExpression = Expression.parse(expressionString, usingCache: false)
    let expression = Expression(parsedExpression, constants: ["foo": 4, "bar": 5])