"
25 | }
26 | """)
27 | )
28 |
--------------------------------------------------------------------------------
/Snapshots/Issues/678.swift:
--------------------------------------------------------------------------------
1 | // swiftformat:options --swiftversion 5.2
2 |
3 | final class Value
{
4 | private let provider: () -> T
5 |
6 | init(provider: @escaping () -> T) {
7 | self.provider = provider
8 | }
9 | }
10 |
11 | final class Consumer {
12 | private(set) lazy var value = Value { [unowned self] in
13 | self.someProvider()
14 | }
15 |
16 | private func someProvider() -> String {
17 | "string"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Snapshots/Issues/681.swift:
--------------------------------------------------------------------------------
1 | someController = SomeController(action: { [unowned self] in
2 | self.value = true; self.actionCalled()
3 | },
4 | otherParameter: parameter)
5 |
--------------------------------------------------------------------------------
/Snapshots/Issues/682.swift:
--------------------------------------------------------------------------------
1 | enum Flag: String, Codable {
2 | case `init`
3 | }
4 |
5 | let flags: [Flag] = [.`init`]
6 |
--------------------------------------------------------------------------------
/Snapshots/Issues/687.swift:
--------------------------------------------------------------------------------
1 | // swiftformat:options --self init-only
2 |
3 | protocol Test where Self: UIViewController {
4 | static func abc() -> Self
5 | }
6 |
--------------------------------------------------------------------------------
/Snapshots/Issues/794.swift:
--------------------------------------------------------------------------------
1 | protocol P1 {}
2 |
3 | extension P1 {
4 | public func f() {}
5 | }
6 |
7 | public protocol P2 {
8 | public func f()
9 | }
10 |
11 | public struct S: P1, P2 {}
12 |
--------------------------------------------------------------------------------
/Snapshots/Layout/.swift-version:
--------------------------------------------------------------------------------
1 | 4.0
2 |
--------------------------------------------------------------------------------
/Snapshots/Layout/.swiftformat:
--------------------------------------------------------------------------------
1 | --header "// Copyright © 2017 Schibsted. All rights reserved."
2 | --binarygrouping 8,8
3 | --decimalgrouping ignore
4 | --importgrouping testable-bottom
5 | --modifierorder public,override
6 |
--------------------------------------------------------------------------------
/Snapshots/Layout/Benchmark/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | // Copyright © 2017 Schibsted. All rights reserved.
2 |
3 | import UIKit
4 |
5 | @UIApplicationMain
6 | class AppDelegate: UIResponder, UIApplicationDelegate {
7 | var window: UIWindow?
8 |
9 | func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
10 | window = UIWindow()
11 | window?.rootViewController = ViewController()
12 | window?.makeKeyAndVisible()
13 | return true
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Snapshots/Layout/EditorExtension/Application/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | // Copyright © 2017 Schibsted. All rights reserved.
2 |
3 | import Cocoa
4 |
5 | @NSApplicationMain
6 | class AppDelegate: NSObject, NSApplicationDelegate {}
7 |
--------------------------------------------------------------------------------
/Snapshots/Layout/EditorExtension/Application/ViewController.swift:
--------------------------------------------------------------------------------
1 | // Copyright © 2017 Schibsted. All rights reserved.
2 |
3 | import Cocoa
4 |
5 | class ViewController: NSViewController {}
6 |
--------------------------------------------------------------------------------
/Snapshots/Layout/EditorExtension/Extension/SourceEditorExtension.swift:
--------------------------------------------------------------------------------
1 | // Copyright © 2017 Schibsted. All rights reserved.
2 |
3 | import Foundation
4 | import XcodeKit
5 |
6 | class SourceEditorExtension: NSObject, XCSourceEditorExtension {}
7 |
--------------------------------------------------------------------------------
/Snapshots/Layout/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Schibsted Products and Technology
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Snapshots/Layout/Layout/.swiftformat:
--------------------------------------------------------------------------------
1 | --exclude Vendor
2 |
--------------------------------------------------------------------------------
/Snapshots/Layout/Layout/Layout+Testing.swift:
--------------------------------------------------------------------------------
1 | // Copyright © 2017 Schibsted. All rights reserved.
2 |
3 | public extension Layout {
4 | /// Clear all Layout caches
5 | static func clearAllCaches() {
6 | Expression.clearCache()
7 | clearParsedExpressionCache()
8 | clearLayoutExpressionCache()
9 | clearRuntimeTypeCache()
10 | clearExpressionTypes()
11 | clearLayoutLoaderCache()
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Snapshots/Layout/Layout/Layout.swift:
--------------------------------------------------------------------------------
1 | // Copyright © 2017 Schibsted. All rights reserved.
2 |
3 | import Foundation
4 |
5 | /// Internal struct used to store
6 | /// serialized layouts
7 | public struct Layout {
8 | var className: String
9 | var id: String?
10 | var expressions: [String: String]
11 | var parameters: [String: RuntimeType]
12 | var macros: [String: String]
13 | var children: [Layout]
14 | var body: String?
15 | var xmlPath: String?
16 | var templatePath: String?
17 | var childrenTagIndex: Int?
18 | var relativePath: String?
19 | var rootURL: URL?
20 |
21 | func getClass() throws -> AnyClass {
22 | guard let cls: AnyClass = classFromString(className) else {
23 | throw LayoutError.message("Unknown class \(className)")
24 | }
25 | return cls
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Snapshots/Layout/Layout/LayoutBacked.swift:
--------------------------------------------------------------------------------
1 | // Copyright © 2017 Schibsted. All rights reserved.
2 |
3 | import Foundation
4 |
5 | /// Protocol for views or controllers that are backed by a LayoutNode
6 | /// Exposes the node reference so that the view can update itself
7 | public protocol LayoutBacked: class {
8 | /** weak */ var layoutNode: LayoutNode? { get }
9 | }
10 |
11 | public extension LayoutBacked where Self: NSObject {
12 | /// Default implementation of the layoutNode property
13 | internal(set) weak var layoutNode: LayoutNode? {
14 | get { return _layoutNode }
15 | set { _setLayoutNode(layoutNode, retained: false) }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Snapshots/Layout/Layout/LayoutDelegate.swift:
--------------------------------------------------------------------------------
1 | // Copyright © 2017 Schibsted. All rights reserved.
2 |
3 | import Foundation
4 |
5 | /// Optional delegate protocol to be implemented by a LayoutNode's owner
6 | public protocol LayoutDelegate: class {
7 | /// Handle errors produced by the layout during an update
8 | /// The default implementation displays a red box error alert using the LayoutConsole
9 | func layoutError(_ error: LayoutError)
10 |
11 | /// A variable or constant value inherited from the delegate
12 | /// Layout will call this method for any expression symbol that it doesn't
13 | /// recognize. If the method returns nil, an error will be thrown
14 | func layoutValue(forKey key: String) throws -> Any?
15 | }
16 |
17 | public extension LayoutDelegate {
18 | /// Default error handler implementation - bubbles error up to the first responder
19 | /// that will handle it, or displays LayoutConsole if no handler is found
20 | func layoutError(_ error: LayoutError) {
21 | DispatchQueue.main.async {
22 | var responder = (self as? UIResponder)?.next
23 | while responder != nil {
24 | if let errorHandler = responder as? LayoutLoading {
25 | errorHandler.layoutError(error)
26 | return
27 | }
28 | responder = responder?.next ?? (responder as? UIViewController)?.parent
29 | }
30 | LayoutConsole.showError(error)
31 | }
32 | }
33 |
34 | /// Default implementation - returns nothing
35 | func layoutValue(forKey _: String) throws -> Any? {
36 | return nil
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Snapshots/Layout/Layout/LayoutNode+XML.swift:
--------------------------------------------------------------------------------
1 | // Copyright © 2017 Schibsted. All rights reserved.
2 |
3 | import Foundation
4 |
5 | public extension LayoutNode {
6 | @available(*, deprecated, renamed: "init(xmlData:)")
7 | static func with(xmlData: Data, url: URL? = nil, relativeTo: String? = #file) throws -> LayoutNode {
8 | return try LayoutNode(
9 | layout: Layout(xmlData: xmlData, url: url, relativeTo: relativeTo)
10 | )
11 | }
12 |
13 | /// Creates a LayoutNode from a parse XML file
14 | /// The optional `url` parameter tells Layout where the node was loded from
15 | /// The optional` relativeTo` parameter helps to locate the original source file
16 | convenience init(xmlData: Data, url: URL? = nil, relativeTo: String? = #file) throws {
17 | try self.init(layout: Layout(xmlData: xmlData, url: url, relativeTo: relativeTo))
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Snapshots/Layout/Layout/LayoutViewController.swift:
--------------------------------------------------------------------------------
1 | // Copyright © 2017 Schibsted. All rights reserved.
2 |
3 | import UIKit
4 |
5 | @available(*, deprecated, message: "Use the LayoutLoading protocol instead")
6 | open class LayoutViewController: UIViewController, LayoutLoading {
7 | /// Called immediately after the layoutNode is set. Will not be called
8 | /// in the event of an error, or if layoutNode is set to nil
9 | open func layoutDidLoad(_: LayoutNode) {
10 | // Mimic old behaviour if not overridden
11 | layoutDidLoad()
12 | }
13 |
14 | /// Called immediately after the layoutNode is set. Will not be called
15 | /// in the event of an error, or if layoutNode is set to nil
16 | @available(*, deprecated, message: "Use layoutDidLoad(_ layoutNode:) instead")
17 | open func layoutDidLoad() {
18 | // Override in subclass
19 | }
20 |
21 | /// Default error handler implementation - bubbles error up to the first responder
22 | /// that will handle it, or displays LayoutConsole if no handler is found
23 | open func layoutError(_ error: LayoutError) {
24 | DispatchQueue.main.async {
25 | var responder = self.next
26 | while responder != nil {
27 | if let errorHandler = responder as? LayoutLoading {
28 | errorHandler.layoutError(error)
29 | return
30 | }
31 | responder = responder?.next ?? (responder as? UIViewController)?.parent
32 | }
33 | LayoutConsole.showError(error)
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Snapshots/Layout/Layout/Shared/Shared.swift:
--------------------------------------------------------------------------------
1 | // Copyright © 2017 Schibsted. All rights reserved.
2 |
3 | /// Expressions that affect layout
4 | /// These are common to every Layout node
5 | /// These are all of type CGFloat, apart from `center` which is a CGPoint
6 | let layoutSymbols: Set = [
7 | "left", "right", "leading", "trailing",
8 | "width", "top", "bottom", "height", "center",
9 | "center.x", "center.y", "firstBaseline", "lastBaseline",
10 | ]
11 |
12 | /// HTML tags that should not contain children
13 | /// http://w3c.github.io/html/syntax.html#void-elements
14 | let emptyHTMLTags: Set = [
15 | "area", "base", "br", "col", "embed", "hr",
16 | "img", "input", "link", "meta", "param",
17 | "source", "track", "wbr",
18 | ]
19 |
--------------------------------------------------------------------------------
/Snapshots/Layout/Layout/TitleTextAttributes.swift:
--------------------------------------------------------------------------------
1 | // Copyright © 2017 Schibsted. All rights reserved.
2 |
3 | import UIKit
4 |
5 | /// Common attributes shared by any view with a titleTextAttributes property
6 | /// The purpose of this protocol is to ensure consistent support between components
7 | @objc protocol TitleTextAttributes {
8 | var titleColor: UIColor? { get set }
9 | var titleFont: UIFont? { get set }
10 | }
11 |
--------------------------------------------------------------------------------
/Snapshots/Layout/LayoutTests/EnumExpressionTests.swift:
--------------------------------------------------------------------------------
1 | // Copyright © 2017 Schibsted. All rights reserved.
2 |
3 | import XCTest
4 | @testable import Layout
5 |
6 | class EnumExpressionTests: XCTestCase {
7 | func testContentModeLiteral() {
8 | let node = LayoutNode(
9 | expressions: [
10 | "contentMode": "center",
11 | ]
12 | )
13 | let expected = UIView.ContentMode.center
14 | XCTAssertEqual(try node.value(forSymbol: "contentMode") as? UIView.ContentMode, expected)
15 | }
16 |
17 | func testContentModeConstant() {
18 | let expected = UIView.ContentMode.center
19 | let node = LayoutNode(
20 | constants: [
21 | "mode": expected,
22 | ],
23 | expressions: [
24 | "contentMode": "mode",
25 | ]
26 | )
27 | XCTAssertEqual(try node.value(forSymbol: "contentMode") as? UIView.ContentMode, expected)
28 | }
29 |
30 | func testReturnKeyLiteral() {
31 | let node = LayoutNode(
32 | view: UITextField(),
33 | expressions: [
34 | "returnKeyType": "go",
35 | ]
36 | )
37 | let expected = UIReturnKeyType.go
38 | XCTAssertEqual(try node.value(forSymbol: "returnKeyType") as? UIReturnKeyType, expected)
39 | }
40 |
41 | func testReturnKeyConstant() {
42 | let expected = UIReturnKeyType.go
43 | let node = LayoutNode(
44 | view: UITextField(),
45 | constants: [
46 | "type": expected,
47 | ],
48 | expressions: [
49 | "returnKeyType": "type",
50 | ]
51 | )
52 | XCTAssertEqual(try node.value(forSymbol: "returnKeyType") as? UIReturnKeyType, expected)
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Snapshots/Layout/LayoutTests/OptionSetExpressionTests.swift:
--------------------------------------------------------------------------------
1 | // Copyright © 2017 Schibsted. All rights reserved.
2 |
3 | import XCTest
4 | @testable import Layout
5 |
6 | class OptionSetExpressionTests: XCTestCase {
7 | func testSingleDataDetectorType() {
8 | let node = LayoutNode(
9 | view: UITextView(),
10 | expressions: [
11 | "dataDetectorTypes": "phoneNumber",
12 | ]
13 | )
14 | XCTAssertEqual(try node.value(forSymbol: "dataDetectorTypes") as? UIDataDetectorTypes, .phoneNumber)
15 | }
16 |
17 | func testMultipleDataDetectorTypes() {
18 | let node = LayoutNode(
19 | view: UITextView(),
20 | expressions: [
21 | "dataDetectorTypes": "phoneNumber, address, link",
22 | ]
23 | )
24 | XCTAssertEqual(try node.value(forSymbol: "dataDetectorTypes") as? UIDataDetectorTypes, [.phoneNumber, .address, .link])
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Snapshots/Layout/LayoutTests/SelectorExpressionTests.swift:
--------------------------------------------------------------------------------
1 | // Copyright © 2017 Schibsted. All rights reserved.
2 |
3 | import XCTest
4 | @testable import Layout
5 |
6 | private class TestView: UIView {
7 | @objc var action: Selector?
8 | }
9 |
10 | private class TestViewController: UIViewController {
11 | @objc func foo(_: UIView) {
12 | print("It works!")
13 | }
14 | }
15 |
16 | class SelectorExpressionTests: XCTestCase {
17 | func testSetControlAction() {
18 | let node = LayoutNode(view: UIControl(), expressions: ["touchUpInside": "foo:"])
19 | let viewController = TestViewController()
20 | XCTAssertNoThrow(try node.mount(in: viewController))
21 | let control = node.view as! UIControl
22 | XCTAssertEqual(control.actions(forTarget: viewController, forControlEvent: .touchUpInside)?.first, "foo:")
23 | }
24 |
25 | func testSetCustomSelector() {
26 | let node = LayoutNode(view: TestView(), expressions: ["action": "foo:"])
27 | node.update()
28 | XCTAssertNotNil(node.view.action)
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Snapshots/Layout/LayoutTests/TableViewTests.swift:
--------------------------------------------------------------------------------
1 | // Copyright © 2017 Schibsted. All rights reserved.
2 |
3 | import UIKit
4 | import XCTest
5 | @testable import Layout
6 |
7 | class TableViewController: UIViewController, LayoutLoading, UITableViewDataSource, UITableViewDelegate {
8 | @objc var tableView: UITableView?
9 | var didLoadRows = false
10 |
11 | override func viewDidLoad() {
12 | super.viewDidLoad()
13 | edgesForExtendedLayout = []
14 | loadLayout(named: "TableViewTest.xml", bundle: Bundle(for: type(of: self)))
15 | }
16 |
17 | func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int {
18 | didLoadRows = true
19 | return 5
20 | }
21 |
22 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
23 | let node = tableView.dequeueReusableCellNode(withIdentifier: "testCell", for: indexPath)
24 | node.setState([])
25 | return node.view as! UITableViewCell
26 | }
27 | }
28 |
29 | class TableViewTests: XCTestCase {
30 | func testTableCellSizing() {
31 | let vc = TableViewController()
32 | vc.view.frame = CGRect(x: 0, y: 0, width: 512, height: 512)
33 | XCTAssertNotNil(vc.tableView)
34 | vc.tableView?.reloadData()
35 | XCTAssert(vc.didLoadRows)
36 | guard let cell = vc.tableView?.cellForRow(at: IndexPath(row: 0, section: 0)) else {
37 | XCTFail()
38 | return
39 | }
40 | XCTAssertEqual(cell.frame.width, 512)
41 | guard let cellLayoutView = cell.contentView.subviews.first else {
42 | XCTFail()
43 | return
44 | }
45 | XCTAssertNotNil(cellLayoutView._layoutNode)
46 | XCTAssertEqual(cellLayoutView.frame.width, 512)
47 | XCTAssertEqual(cellLayoutView.frame.origin.x, 0)
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Snapshots/Layout/LayoutToolTests/RenamerTests.swift:
--------------------------------------------------------------------------------
1 | // Copyright © 2017 Schibsted. All rights reserved.
2 |
3 | import XCTest
4 |
5 | class RenamerTests: XCTestCase {
6 | func testRenameStandaloneVariable() {
7 | let input = ""
8 | let expected = "\n"
9 | let output = try! rename("foo", to: "bar", in: input)
10 | XCTAssertEqual(output, expected)
11 | }
12 |
13 | func testRenameVariableInExpression() {
14 | let input = ""
15 | let expected = "\n"
16 | let output = try! rename("foo", to: "bar", in: input)
17 | XCTAssertEqual(output, expected)
18 | }
19 |
20 | func testNoRenameTextInStringExpression() {
21 | let input = ""
22 | let expected = "\n"
23 | let output = try! rename("foo", to: "bar", in: input)
24 | XCTAssertEqual(output, expected)
25 | }
26 |
27 | func testRenameVariableInEscapedStringExpression() {
28 | let input = ""
29 | let expected = "\n"
30 | let output = try! rename("foo", to: "bar", in: input)
31 | XCTAssertEqual(output, expected)
32 | }
33 |
34 | func testRenameClass() {
35 | let input = ""
36 | let expected = "\n"
37 | let output = try! rename("Foo", to: "Bar", in: input)
38 | XCTAssertEqual(output, expected)
39 | }
40 |
41 | func testNoRenameHTML() {
42 | let input = "\n foo\n\n"
43 | let expected = "\n foo\n\n"
44 | let output = try! rename("center", to: "centered", in: input)
45 | XCTAssertEqual(output, expected)
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Snapshots/Layout/LayoutToolTests/ReturnCodeTests.swift:
--------------------------------------------------------------------------------
1 | // Copyright © 2017 Schibsted. All rights reserved.
2 |
3 | import XCTest
4 |
5 | class ReturnCodeTests: XCTestCase {
6 | func testReturnsSuccessCodeForExpectedInput() {
7 | XCTAssertEqual(processArguments(["LayoutTool", "version"]), .success)
8 | }
9 |
10 | func testReturnsErrorCodeWhenErrorsOccur() {
11 | XCTAssertEqual(processArguments(["LayoutTool"]), .failure)
12 | XCTAssertEqual(processArguments(["LayoutTool", "format"]), .failure)
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Snapshots/Layout/LayoutToolTests/StringsTests.swift:
--------------------------------------------------------------------------------
1 | // Copyright © 2017 Schibsted. All rights reserved.
2 |
3 | import XCTest
4 |
5 | class StringsTests: XCTestCase {
6 | func testFindStringsInAttributes() {
7 | let input = ""
8 | let output = ["bar", "foo"]
9 | XCTAssertEqual(try strings(in: input), output)
10 | }
11 |
12 | func testFindStringsInBody() {
13 | let input = "{strings.foo} {strings.bar}"
14 | let output = ["bar", "foo"]
15 | XCTAssertEqual(try strings(in: input), output)
16 | }
17 |
18 | func testIgnoreDuplicateStrings() {
19 | let input = "{strings.foo} {strings.bar} {strings.foo}"
20 | let output = ["bar", "foo"]
21 | XCTAssertEqual(try strings(in: input), output)
22 | }
23 |
24 | func testFindEscapedString() {
25 | let input = ""
26 | let output = ["hello\nworld"]
27 | XCTAssertEqual(try strings(in: input), output)
28 | }
29 |
30 | func testFindParameterizedString() {
31 | let input = ""
32 | let output = ["foo"]
33 | XCTAssertEqual(try strings(in: input), output)
34 | }
35 |
36 | func testFindEscapedParameterizedString() {
37 | let input = ""
38 | let output = ["foo"]
39 | XCTAssertEqual(try strings(in: input), output)
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Snapshots/Layout/SampleApp/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | // Copyright © 2017 Schibsted. All rights reserved.
2 |
3 | import Layout
4 | import UIKit
5 |
6 | #if !swift(>=4.2)
7 |
8 | extension UIApplication {
9 | typealias LaunchOptionsKey = UIApplicationLaunchOptionsKey
10 | }
11 |
12 | #endif
13 |
14 | @UIApplicationMain
15 | class AppDelegate: UIResponder, UIApplicationDelegate {
16 | var window: UIWindow?
17 |
18 | func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
19 | LayoutNode.useLegacyLayoutMode = false
20 |
21 | window = UIWindow()
22 | window?.rootViewController = ExamplesViewController()
23 | window?.makeKeyAndVisible()
24 | return true
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Snapshots/Layout/SampleApp/BoxesViewController.swift:
--------------------------------------------------------------------------------
1 | // Copyright © 2017 Schibsted. All rights reserved.
2 |
3 | import Layout
4 | import UIKit
5 |
6 | class BoxesViewController: UIViewController {
7 | var toggled = false {
8 | didSet {
9 | layoutNode?.setState(["isToggled": toggled])
10 | }
11 | }
12 |
13 | @IBOutlet var layoutNode: LayoutNode? {
14 | didSet {
15 | layoutNode?.setState(["isToggled": toggled])
16 | }
17 | }
18 |
19 | @IBAction func setToggled() {
20 | UIView.animate(withDuration: 0.4) {
21 | self.toggled = true
22 | }
23 | }
24 |
25 | @IBAction func setUntoggled() {
26 | UIView.animate(withDuration: 0.4) {
27 | self.toggled = false
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Snapshots/Layout/SampleApp/CollectionViewController.swift:
--------------------------------------------------------------------------------
1 | // Copyright © 2017 Schibsted. All rights reserved.
2 |
3 | import Layout
4 | import UIKit
5 |
6 | private let images = [
7 | UIImage(named: "Boxes"),
8 | UIImage(named: "Pages"),
9 | UIImage(named: "Text"),
10 | UIImage(named: "Table"),
11 | UIImage(named: "Collection"),
12 | UIImage(named: "Rocket"),
13 | ]
14 |
15 | class CollectionViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
16 | @IBOutlet var collectionView: UICollectionView? {
17 | didSet {
18 | collectionView?.registerLayout(
19 | named: "CollectionCell.xml",
20 | forCellReuseIdentifier: "standaloneCell"
21 | )
22 | }
23 | }
24 |
25 | func collectionView(_: UICollectionView, numberOfItemsInSection _: Int) -> Int {
26 | return 500
27 | }
28 |
29 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
30 | let identifier = (indexPath.row % 2 == 0) ? "templateCell" : "standaloneCell"
31 | let node = collectionView.dequeueReusableCellNode(withIdentifier: identifier, for: indexPath)
32 | let image = images[indexPath.row % images.count]!
33 |
34 | node.setState([
35 | "row": indexPath.row,
36 | "image": image,
37 | "whiteImage": image.withRenderingMode(.alwaysOriginal),
38 | ])
39 |
40 | return node.view as! UICollectionViewCell
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Snapshots/Layout/SampleApp/PagesViewController.swift:
--------------------------------------------------------------------------------
1 | // Copyright © 2017 Schibsted. All rights reserved.
2 |
3 | import UIKit
4 |
5 | class PagesViewController: UIViewController, UIScrollViewDelegate {
6 | @IBOutlet var scrollView: UIScrollView?
7 | @IBOutlet var pageControl: UIPageControl?
8 |
9 | func scrollViewDidScroll(_ scrollView: UIScrollView) {
10 | if scrollView === self.scrollView {
11 | pageControl?.currentPage = Int(round(scrollView.contentOffset.x / scrollView.frame.width))
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Snapshots/Layout/SampleApp/TableViewController.swift:
--------------------------------------------------------------------------------
1 | // Copyright © 2017 Schibsted. All rights reserved.
2 |
3 | import Layout
4 | import UIKit
5 |
6 | private let images = [
7 | UIImage(named: "Boxes"),
8 | UIImage(named: "Pages"),
9 | UIImage(named: "Text"),
10 | UIImage(named: "Table"),
11 | UIImage(named: "Collection"),
12 | UIImage(named: "Rocket"),
13 | ]
14 |
15 | class TableViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
16 | @IBOutlet var tableView: UITableView? {
17 | didSet {
18 | tableView?.registerLayout(
19 | named: "TableCell.xml",
20 | forCellReuseIdentifier: "standaloneCell"
21 | )
22 | }
23 | }
24 |
25 | func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int {
26 | return 500
27 | }
28 |
29 | func tableView(_ tableView: UITableView, viewForHeaderInSection _: Int) -> UIView? {
30 | let node = tableView.dequeueReusableHeaderFooterNode(withIdentifier: "templateHeader")
31 | return node?.view as? UITableViewHeaderFooterView
32 | }
33 |
34 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
35 | let identifier = (indexPath.row % 2 == 0) ? "templateCell" : "standaloneCell"
36 | let node = tableView.dequeueReusableCellNode(withIdentifier: identifier, for: indexPath)
37 | let image = images[indexPath.row % images.count]!
38 |
39 | node.setState([
40 | "row": indexPath.row,
41 | "image": image,
42 | "whiteImage": image.withRenderingMode(.alwaysOriginal),
43 | ])
44 |
45 | return node.view as! UITableViewCell
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Snapshots/Layout/SampleApp/UIColor+Hex.swift:
--------------------------------------------------------------------------------
1 | // Copyright © 2017 Schibsted. All rights reserved.
2 |
3 | import UIKit
4 |
5 | public extension UIColor {
6 | convenience init?(hexString: String) {
7 | if hexString.hasPrefix("#") {
8 | var string = String(hexString.dropFirst())
9 | switch string.count {
10 | case 3:
11 | string += "f"
12 | fallthrough
13 | case 4:
14 | let chars = Array(string)
15 | let red = chars[0]
16 | let green = chars[1]
17 | let blue = chars[2]
18 | let alpha = chars[3]
19 | string = "\(red)\(red)\(green)\(green)\(blue)\(blue)\(alpha)\(alpha)"
20 | case 6:
21 | string += "ff"
22 | case 8:
23 | break
24 | default:
25 | return nil
26 | }
27 | if let rgba = Double("0x" + string).flatMap({ UInt32(exactly: $0) }) {
28 | let red = CGFloat((rgba & 0xFF00_0000) >> 24) / 255
29 | let green = CGFloat((rgba & 0x00FF_0000) >> 16) / 255
30 | let blue = CGFloat((rgba & 0x0000_FF00) >> 8) / 255
31 | let alpha = CGFloat((rgba & 0x0000_00FF) >> 0) / 255
32 | self.init(red: red, green: green, blue: blue, alpha: alpha)
33 | return
34 | }
35 | }
36 | return nil
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Snapshots/Layout/Sandbox/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | // Copyright © 2017 Schibsted. All rights reserved.
2 |
3 | import UIKit
4 |
5 | @UIApplicationMain
6 | class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDelegate {
7 | var window: UIWindow?
8 |
9 | func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
10 | window = UIWindow()
11 |
12 | let editController = EditViewController()
13 | window?.rootViewController = UINavigationController(rootViewController: editController)
14 | editController.showPreview(animated: false)
15 |
16 | window?.makeKeyAndVisible()
17 | return true
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Snapshots/Layout/TestFramework/TestFramework.swift:
--------------------------------------------------------------------------------
1 | // Copyright © 2017 Schibsted. All rights reserved.
2 |
3 | import Foundation
4 |
5 | public class TestFrameworkClass {}
6 |
--------------------------------------------------------------------------------
/Snapshots/Layout/UIDesigner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | // Copyright © 2017 Schibsted. All rights reserved.
2 |
3 | import UIKit
4 |
5 | @UIApplicationMain
6 | class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDelegate {
7 | var window: UIWindow?
8 |
9 | func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
10 | precondition(UI_USER_INTERFACE_IDIOM() == .pad,
11 | "This application is designed for iPad only")
12 |
13 | window = UIWindow()
14 |
15 | let splitViewController = UISplitViewController()
16 | let navigationController = UINavigationController()
17 | navigationController.viewControllers = [TreeViewController()]
18 | splitViewController.viewControllers = [navigationController, DesignViewController()]
19 | splitViewController.delegate = self
20 |
21 | // Hide the tree view for now, as it needs some improvements
22 | splitViewController.preferredDisplayMode = .primaryHidden
23 |
24 | window?.rootViewController = splitViewController
25 | window?.makeKeyAndVisible()
26 |
27 | return true
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Snapshots/Parsing/.swiftformat:
--------------------------------------------------------------------------------
1 | # format options
2 |
3 | --patternlet inline
4 | --self insert
5 |
6 | # rules
7 |
8 | --disable trailingClosures, blankLinesAtStartOfScope, sortedImports
9 |
--------------------------------------------------------------------------------
/Snapshots/Parsing/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Nick Lockwood
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Snapshots/Parsing/Sources/Formatter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Formatter.swift
3 | // Parsing
4 | //
5 | // Created by Nick Lockwood on 04/09/2018.
6 | // Copyright © 2018 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | // MARK: interface
12 |
13 | public func format(_ program: [Statement]) -> String {
14 | var output = ""
15 | for statement in program {
16 | output.append(statement.description + "\n")
17 | }
18 | return output
19 | }
20 |
21 | // MARK: implementation
22 |
23 | extension Statement: CustomStringConvertible {
24 |
25 | public var description: String {
26 | switch self {
27 | case .declaration(name: let name, value: let expression):
28 | return "let \(name) = \(expression)"
29 | case .print(let expression):
30 | return "print \(expression)"
31 | }
32 | }
33 | }
34 |
35 | extension Expression: CustomStringConvertible {
36 |
37 | public var description: String {
38 | switch self {
39 | case .number(let double):
40 | return String(format: "%g", double)
41 | case .string(let string):
42 | let escapedString = string
43 | .replacingOccurrences(of: "\\", with: "\\\\")
44 | .replacingOccurrences(of: "\"", with: "\\\"")
45 | return "\"\(escapedString)\""
46 | case .variable(let name):
47 | return name
48 | case .addition(lhs: let lhs, rhs: let rhs):
49 | return "\(lhs) + \(rhs)"
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/Snapshots/Parsing/Sources/Parsing.h:
--------------------------------------------------------------------------------
1 | //
2 | // Parsing.h
3 | // Parsing
4 | //
5 | // Created by Nick Lockwood on 03/09/2018.
6 | // Copyright © 2018 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | //! Project version number for Parsing.
12 | FOUNDATION_EXPORT double ParsingVersionNumber;
13 |
14 | //! Project version string for Parsing.
15 | FOUNDATION_EXPORT const unsigned char ParsingVersionString[];
16 |
17 | // In this header, you should import all the public headers of your framework using statements like #import
18 |
19 |
20 |
--------------------------------------------------------------------------------
/Snapshots/Parsing/Tests/FormatterTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FormatterTests.swift
3 | // ParsingTests
4 | //
5 | // Created by Nick Lockwood on 04/09/2018.
6 | // Copyright © 2018 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import Parsing
11 |
12 | class FormatterTests: XCTestCase {
13 |
14 | func testFormatting() throws {
15 | let input = """
16 | let foo = 5 + 6
17 | let bar = "hello\\\\world"
18 | let baz = "goodbye \\"world\\""
19 | print foo + bar + baz
20 | """
21 | let program = try parse(input)
22 | let output = format(program)
23 | XCTAssertEqual(output, input + "\n")
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Snapshots/README.md:
--------------------------------------------------------------------------------
1 | The files in this directory constitute a regression test suite, used to check for unintended changes to SwiftFormat's behavior.
2 |
3 | Projects in this directory include only the swift files and documentation/licence information. There are missing their project files and other resources, and in most cases will not compile. If you are interested in using these libraries, you will find them on Github.
4 |
5 | If you would like to add your own projects to this test suit, please [open a pull request](https://nicklockwood/SwiftFormat). Be sure to remove all non-swift files apart from documentation and SwiftFormat configuration.
6 |
--------------------------------------------------------------------------------
/Snapshots/Sprinter/.swift-version:
--------------------------------------------------------------------------------
1 | 4.0
--------------------------------------------------------------------------------
/Snapshots/Sprinter/.swiftformat:
--------------------------------------------------------------------------------
1 | --hexgrouping ignore
2 | --decimalgrouping ignore
3 | --enable isEmpty
4 | --ifdef no-indent
5 |
--------------------------------------------------------------------------------
/Snapshots/Sprinter/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Nick Lockwood
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Snapshots/Sprinter/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:4.0
2 | import PackageDescription
3 |
4 | let package = Package(
5 | name: "Sprinter"
6 | )
7 |
--------------------------------------------------------------------------------
/Snapshots/SwiftUI/.swiftformat:
--------------------------------------------------------------------------------
1 | --swiftversion 5.2
2 | --indent 2
--------------------------------------------------------------------------------
/Snapshots/SwiftUI/Bookworm/EmojiRatingView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EmojiRatingView.swift
3 | // Bookworm
4 | //
5 | // Created by Nick Lockwood on 30/06/2020.
6 | // Copyright © 2020 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct EmojiRatingView: View {
12 | let rating: Int16
13 |
14 | var body: some View {
15 | switch rating {
16 | case 1:
17 | return Text("😴")
18 | case 2:
19 | return Text("☹️")
20 | case 3:
21 | return Text("😐")
22 | case 4:
23 | return Text("😄")
24 | default:
25 | return Text("🤩")
26 | }
27 | }
28 | }
29 |
30 | struct EmojiRatingView_Previews: PreviewProvider {
31 | static var previews: some View {
32 | EmojiRatingView(rating: 3)
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Snapshots/SwiftUI/Bookworm/RatingView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RatingView.swift
3 | // Bookworm
4 | //
5 | // Created by Nick Lockwood on 30/06/2020.
6 | // Copyright © 2020 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct RatingView: View {
12 | @Binding var rating: Int
13 |
14 | var label = ""
15 |
16 | var maximumRating = 5
17 |
18 | var offImage: Image?
19 | var onImage = Image(systemName: "star.fill")
20 |
21 | var offColor = Color.gray
22 | var onColor = Color.yellow
23 |
24 | var body: some View {
25 | HStack {
26 | if label.isEmpty == false {
27 | Text(label)
28 | }
29 |
30 | ForEach(1 ..< maximumRating + 1) { number in
31 | self.image(for: number)
32 | .foregroundColor(number > self.rating ? self.offColor : self.onColor)
33 | .onTapGesture {
34 | self.rating = number
35 | }
36 | }
37 | }
38 | }
39 |
40 | func image(for number: Int) -> Image {
41 | number > rating ? offImage ?? onImage : onImage
42 | }
43 | }
44 |
45 | struct RatingView_Previews: PreviewProvider {
46 | static var previews: some View {
47 | RatingView(rating: .constant(4))
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Snapshots/SwiftUI/CupcakeCorner/AddressView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AddressView.swift
3 | // CupcakeCorner
4 | //
5 | // Created by Nick Lockwood on 28/06/2020.
6 | // Copyright © 2020 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct AddressView: View {
12 | @ObservedObject var order = Order()
13 |
14 | var body: some View {
15 | Form {
16 | Section {
17 | TextField("Name", text: $order.data.name)
18 | TextField("Street Address", text: $order.data.streetAddress)
19 | TextField("City", text: $order.data.city)
20 | TextField("Zip", text: $order.data.zip)
21 | }
22 |
23 | Section {
24 | NavigationLink(destination: CheckoutView(order: order)) {
25 | Text("Check out")
26 | }
27 | }.disabled(!order.data.hasValidAddress)
28 | }
29 | .navigationBarTitle("Delivery details", displayMode: .inline)
30 | }
31 | }
32 |
33 | struct AddressView_Previews: PreviewProvider {
34 | static var previews: some View {
35 | AddressView(order: Order())
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Snapshots/SwiftUI/CupcakeCorner/ContentView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContentView.swift
3 | // CupcakeCorner
4 | //
5 | // Created by Nick Lockwood on 28/06/2020.
6 | // Copyright © 2020 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct ContentView: View {
12 | @ObservedObject var order = Order()
13 |
14 | var body: some View {
15 | NavigationView {
16 | Form {
17 | Section {
18 | Picker("Select you cake type", selection: $order.data.type) {
19 | ForEach(0 ..< Order.types.count) {
20 | Text(Order.types[$0])
21 | }
22 | }
23 |
24 | Stepper(value: $order.data.quantity, in: 3 ... 20) {
25 | Text("Number of cakes: \(order.data.quantity)")
26 | }
27 | }
28 |
29 | Section {
30 | Toggle(isOn: $order.data.specialRequestEnabled.animation()) {
31 | Text("Any special requests?")
32 | }
33 |
34 | if order.data.specialRequestEnabled {
35 | Toggle(isOn: $order.data.extraFrosting) {
36 | Text("Add extra frosting")
37 | }
38 |
39 | Toggle(isOn: $order.data.addSprinkles) {
40 | Text("Add extra sprinkles")
41 | }
42 | }
43 | }
44 |
45 | Section {
46 | NavigationLink(destination: AddressView(order: order)) {
47 | Text("Delivery details")
48 | }
49 | }
50 | }
51 | .navigationBarTitle("Cupcake Corner")
52 | }
53 | }
54 | }
55 |
56 | struct ContentView_Previews: PreviewProvider {
57 | static var previews: some View {
58 | ContentView()
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/Snapshots/SwiftUI/CupcakeCorner/Order.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Order.swift
3 | // CupcakeCorner
4 | //
5 | // Created by Nick Lockwood on 28/06/2020.
6 | // Copyright © 2020 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class Order: ObservableObject, Codable {
12 | struct Data: Codable {
13 | var type = 0
14 | var quantity = 3
15 |
16 | var specialRequestEnabled = false {
17 | didSet {
18 | if specialRequestEnabled == false {
19 | extraFrosting = false
20 | addSprinkles = false
21 | }
22 | }
23 | }
24 |
25 | var extraFrosting = false
26 | var addSprinkles = false
27 |
28 | var name = ""
29 | var streetAddress = ""
30 | var city = ""
31 | var zip = ""
32 |
33 | var hasValidAddress: Bool {
34 | if name.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
35 | || streetAddress.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
36 | || city.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
37 | || zip.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
38 | {
39 | return false
40 | }
41 | return true
42 | }
43 |
44 | var cost: Double {
45 | var cost = Double(quantity * 2)
46 | cost += Double(type) / 2
47 |
48 | if extraFrosting {
49 | cost += Double(quantity)
50 | }
51 |
52 | if addSprinkles {
53 | cost += Double(quantity) / 2
54 | }
55 |
56 | return cost
57 | }
58 | }
59 |
60 | static let types = ["Vanilla", "Strawberry", "Chocolate", "Rainbow"]
61 |
62 | @Published var data = Data()
63 |
64 | init() {}
65 |
66 | func encode(to encoder: Encoder) throws {
67 | try data.encode(to: encoder)
68 | }
69 |
70 | required init(from decoder: Decoder) throws {
71 | data = try Data(from: decoder)
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/Snapshots/SwiftUI/Moonshot/Astronaut.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Astronaut.swift
3 | // Moonshot
4 | //
5 | // Created by Nick Lockwood on 27/06/2020.
6 | // Copyright © 2020 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | struct Astronaut: Codable, Identifiable {
10 | var id: String
11 | var name: String
12 | var description: String
13 | }
14 |
--------------------------------------------------------------------------------
/Snapshots/SwiftUI/Moonshot/Bundle+Decodable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Bundle+Decodable.swift
3 | // Moonshot
4 | //
5 | // Created by Nick Lockwood on 27/06/2020.
6 | // Copyright © 2020 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension Bundle {
12 | func decode(_ file: String) -> T {
13 | guard let url = url(forResource: file, withExtension: nil) else {
14 | fatalError("Failed to locate \(file) in bundle.")
15 | }
16 |
17 | guard let data = try? Data(contentsOf: url) else {
18 | fatalError("Failed to load \(file) from bundle.")
19 | }
20 |
21 | let decoder = JSONDecoder()
22 | let dateFormatter = DateFormatter()
23 | dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
24 | dateFormatter.locale = Locale(identifier: "en_US_POSIX")
25 | dateFormatter.dateFormat = "y-MM-dd"
26 | decoder.dateDecodingStrategy = .formatted(dateFormatter)
27 | guard let loaded = try? decoder.decode(T.self, from: data) else {
28 | fatalError("Failed to decode \(file) from bundle.")
29 | }
30 |
31 | return loaded
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Snapshots/SwiftUI/Moonshot/ContentView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContentView.swift
3 | // Moonshot
4 | //
5 | // Created by Nick Lockwood on 27/06/2020.
6 | // Copyright © 2020 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct ContentView: View {
12 | let astronauts: [Astronaut] = Bundle.main.decode("astronauts.json")
13 | let missions: [Mission] = Bundle.main.decode("missions.json")
14 |
15 | @State var showCrew = false
16 |
17 | var body: some View {
18 | NavigationView {
19 | List(missions) { mission in
20 | NavigationLink(
21 | destination: MissionView(mission: mission)
22 | ) {
23 | Image(mission.image)
24 | .resizable()
25 | .scaledToFit()
26 | .frame(width: 44, height: 44)
27 |
28 | VStack(alignment: .leading) {
29 | Text(mission.displayName)
30 | .font(.headline)
31 | Text(self.showCrew ?
32 | self.astronauts(in: mission) :
33 | mission.formattedLaunchDate
34 | )
35 | }
36 | }
37 | }
38 | .navigationBarTitle("Moonshot")
39 | .navigationBarItems(trailing: Button(action: {
40 | self.showCrew.toggle()
41 | }) {
42 | Text(self.showCrew ? "Show Date" : "Show Crew")
43 | })
44 | }
45 | }
46 |
47 | func astronauts(in mission: Mission) -> String {
48 | astronauts.filter { astronaut in
49 | mission.crew.contains(where: { $0.name == astronaut.id })
50 | }
51 | .map(\.name)
52 | .joined(separator: ", ")
53 | }
54 | }
55 |
56 | struct ContentView_Previews: PreviewProvider {
57 | static var previews: some View {
58 | ContentView()
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/Snapshots/SwiftUI/Moonshot/Mission.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Mission.swift
3 | // Moonshot
4 | //
5 | // Created by Nick Lockwood on 27/06/2020.
6 | // Copyright © 2020 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct Mission: Codable, Identifiable {
12 | struct CrewRole: Codable {
13 | var name: String
14 | var role: String
15 | }
16 |
17 | var id: Int
18 | var launchDate: Date?
19 | var crew: [CrewRole]
20 | var description: String
21 |
22 | var displayName: String {
23 | "Apollo \(id)"
24 | }
25 |
26 | var image: String {
27 | "apollo\(id)"
28 | }
29 |
30 | var formattedLaunchDate: String {
31 | guard let date = launchDate else {
32 | return "N/A"
33 | }
34 | let formatter = DateFormatter()
35 | formatter.dateStyle = .long
36 | return formatter.string(from: date)
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Snapshots/SwiftUI/README.md:
--------------------------------------------------------------------------------
1 | These are simple SwiftUI projects based on Paul Hudson's [100 days of SwiftUI](https://www.hackingwithswift.com/100/swiftui) tutorial series.
--------------------------------------------------------------------------------
/Sources/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | $(MARKETING_VERSION)
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | NSHumanReadableCopyright
24 | Copyright © 2016 Nick Lockwood. All rights reserved.
25 | NSPrincipalClass
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/Sources/Rules/AnyObjectProtocol.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AnyObjectProtocol.swift
3 | // SwiftFormat
4 | //
5 | // Created by Nick Lockwood on 1/23/19.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension FormatRule {
12 | /// Prefer `AnyObject` over `class` for class-based protocols
13 | static let anyObjectProtocol = FormatRule(
14 | help: "Prefer `AnyObject` over `class` in protocol definitions."
15 | ) { formatter in
16 | formatter.forEach(.keyword("protocol")) { i, _ in
17 | guard formatter.options.swiftVersion >= "4.1",
18 | let nameIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i, if: {
19 | $0.isIdentifier
20 | }), let colonIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: nameIndex, if: {
21 | $0 == .delimiter(":")
22 | }), let classIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: colonIndex, if: {
23 | $0 == .keyword("class")
24 | })
25 | else {
26 | return
27 | }
28 | formatter.replaceToken(at: classIndex, with: .identifier("AnyObject"))
29 | }
30 | } examples: {
31 | """
32 | ```diff
33 | - protocol Foo: class {}
34 | + protocol Foo: AnyObject {}
35 | ```
36 |
37 | **NOTE:** The guideline to use `AnyObject` instead of `class` was only
38 | introduced in Swift 4.1, so the `anyObjectProtocol` rule is disabled unless the
39 | swift version is set to 4.1 or above.
40 | """
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Sources/Rules/ApplicationMain.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ApplicationMain.swift
3 | // SwiftFormat
4 | //
5 | // Created by Nick Lockwood on 5/20/23.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension FormatRule {
12 | /// Replace the obsolete `@UIApplicationMain` and `@NSApplicationMain`
13 | /// attributes with `@main` in Swift 5.3 and above, per SE-0383
14 | static let applicationMain = FormatRule(
15 | help: """
16 | Replace obsolete @UIApplicationMain and @NSApplicationMain attributes
17 | with @main for Swift 5.3 and above.
18 | """
19 | ) { formatter in
20 | guard formatter.options.swiftVersion >= "5.3" else {
21 | return
22 | }
23 | formatter.forEachToken(where: {
24 | [
25 | .keyword("@UIApplicationMain"),
26 | .keyword("@NSApplicationMain"),
27 | ].contains($0)
28 | }) { i, _ in
29 | formatter.replaceToken(at: i, with: .keyword("@main"))
30 | }
31 | } examples: {
32 | """
33 | ```diff
34 | - @UIApplicationMain
35 | + @main
36 | class AppDelegate: UIResponder, UIApplicationDelegate {}
37 | ```
38 | """
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Sources/Rules/BlankLineAfterImports.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BlankLineAfterImports.swift
3 | // SwiftFormat
4 | //
5 | // Created by Tsungyu Yu on 5/1/22.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension FormatRule {
12 | /// Insert blank line after import statements
13 | static let blankLineAfterImports = FormatRule(
14 | help: "Insert blank line after import statements.",
15 | sharedOptions: ["linebreaks"]
16 | ) { formatter in
17 | formatter.forEach(.keyword("import")) { currentImportIndex, _ in
18 | guard let endOfLine = formatter.index(of: .linebreak, after: currentImportIndex),
19 | var nextIndex = formatter.index(of: .nonSpace, after: endOfLine)
20 | else {
21 | return
22 | }
23 | var keyword: Token = formatter.tokens[nextIndex]
24 | while keyword == .startOfScope("#if") || keyword.isModifierKeyword || keyword.isAttribute,
25 | let index = formatter.index(of: .keyword, after: nextIndex)
26 | {
27 | nextIndex = index
28 | keyword = formatter.tokens[nextIndex]
29 | }
30 | switch formatter.tokens[nextIndex] {
31 | case .linebreak, .keyword("import"), .keyword("#else"), .keyword("#elseif"), .endOfScope("#endif"):
32 | break
33 | default:
34 | formatter.insertLinebreak(at: endOfLine)
35 | }
36 | }
37 | } examples: {
38 | """
39 | ```diff
40 | import A
41 | import B
42 | @testable import D
43 | +
44 | class Foo {
45 | // foo
46 | }
47 | ```
48 | """
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Sources/Rules/BlankLinesBetweenImports.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BlankLinesBetweenImports.swift
3 | // SwiftFormat
4 | //
5 | // Created by Huy Vo on 9/28/21.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension FormatRule {
12 | /// Remove blank lines between import statements
13 | static let blankLinesBetweenImports = FormatRule(
14 | help: """
15 | Remove blank lines between import statements.
16 | """,
17 | disabledByDefault: true,
18 | sharedOptions: ["linebreaks"]
19 | ) { formatter in
20 | formatter.forEach(.keyword("import")) { currentImportIndex, _ in
21 | guard let endOfLine = formatter.index(of: .linebreak, after: currentImportIndex),
22 | let nextImportIndex = formatter.index(of: .nonSpaceOrLinebreak, after: endOfLine, if: {
23 | $0 == .keyword("@testable") || $0 == .keyword("import")
24 | })
25 | else {
26 | return
27 | }
28 |
29 | formatter.replaceTokens(in: endOfLine ..< nextImportIndex, with: formatter.linebreakToken(for: currentImportIndex + 1))
30 | }
31 | } examples: {
32 | """
33 | ```diff
34 | import A
35 | -
36 | import B
37 | import C
38 | -
39 | -
40 | @testable import D
41 | import E
42 | ```
43 | """
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Sources/Rules/ConsecutiveBlankLines.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ConsecutiveBlankLines.swift
3 | // SwiftFormat
4 | //
5 | // Created by Nick Lockwood on 8/30/16.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension FormatRule {
12 | /// Collapse all consecutive blank lines into a single blank line
13 | static let consecutiveBlankLines = FormatRule(
14 | help: "Replace consecutive blank lines with a single blank line."
15 | ) { formatter in
16 | formatter.forEach(.linebreak) { i, _ in
17 | guard let prevIndex = formatter.index(of: .nonSpace, before: i, if: { $0.isLinebreak }) else {
18 | return
19 | }
20 | if let scope = formatter.currentScope(at: i), scope.isMultilineStringDelimiter {
21 | return
22 | }
23 | if let nextIndex = formatter.index(of: .nonSpace, after: i) {
24 | if formatter.tokens[nextIndex].isLinebreak {
25 | formatter.removeTokens(in: i + 1 ... nextIndex)
26 | }
27 | } else if !formatter.options.fragment {
28 | formatter.removeTokens(in: i ..< formatter.tokens.count)
29 | }
30 | }
31 | } examples: {
32 | """
33 | ```diff
34 | func foo() {
35 | let x = "bar"
36 | -
37 |
38 | print(x)
39 | }
40 |
41 | func foo() {
42 | let x = "bar"
43 |
44 | print(x)
45 | }
46 | ```
47 | """
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Sources/Rules/DuplicateImports.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DuplicateImports.swift
3 | // SwiftFormat
4 | //
5 | // Created by Nick Lockwood on 2/7/18.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension FormatRule {
12 | /// Remove duplicate import statements
13 | static let duplicateImports = FormatRule(
14 | help: "Remove duplicate import statements."
15 | ) { formatter in
16 | for var importRanges in formatter.parseImports().reversed() {
17 | for i in importRanges.indices.reversed() {
18 | let range = importRanges.remove(at: i)
19 | guard let j = importRanges.firstIndex(where: { $0.module == range.module }) else {
20 | continue
21 | }
22 | let range2 = importRanges[j]
23 | if Set(range.attributes).isSubset(of: range2.attributes) {
24 | formatter.removeTokens(in: range.range)
25 | continue
26 | }
27 | if j >= i {
28 | formatter.removeTokens(in: range2.range)
29 | importRanges.remove(at: j)
30 | }
31 | importRanges.append(range)
32 | }
33 | }
34 | } examples: {
35 | """
36 | ```diff
37 | import Foo
38 | import Bar
39 | - import Foo
40 | ```
41 |
42 | ```diff
43 | import B
44 | #if os(iOS)
45 | import A
46 | - import B
47 | #endif
48 | ```
49 | """
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Sources/Rules/EmptyBraces.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EmptyBraces.swift
3 | // SwiftFormat
4 | //
5 | // Created by Nick Lockwood on 8/2/18.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension FormatRule {
12 | /// Remove white-space between empty braces
13 | static let emptyBraces = FormatRule(
14 | help: "Remove whitespace inside empty braces.",
15 | options: ["emptybraces"],
16 | sharedOptions: ["linebreaks"]
17 | ) { formatter in
18 | formatter.forEach(.startOfScope("{")) { i, _ in
19 | guard let closingIndex = formatter.index(of: .nonSpaceOrLinebreak, after: i, if: {
20 | $0 == .endOfScope("}")
21 | }) else {
22 | return
23 | }
24 | if let token = formatter.next(.nonSpaceOrComment, after: closingIndex),
25 | [.keyword("else"), .keyword("catch")].contains(token)
26 | {
27 | return
28 | }
29 | let range = i + 1 ..< closingIndex
30 | switch formatter.options.emptyBracesSpacing {
31 | case .noSpace:
32 | formatter.removeTokens(in: range)
33 | case .spaced:
34 | formatter.replaceTokens(in: range, with: .space(" "))
35 | case .linebreak:
36 | formatter.insertSpace(formatter.currentIndentForLine(at: i), at: range.endIndex)
37 | formatter.replaceTokens(in: range, with: formatter.linebreakToken(for: i + 1))
38 | }
39 | }
40 | } examples: {
41 | """
42 | ```diff
43 | - func foo() {
44 | -
45 | - }
46 |
47 | + func foo() {}
48 | ```
49 | """
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Sources/Rules/EmptyExtensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EmptyExtensions.swift
3 | // SwiftFormat
4 | //
5 | // Created by Manny Lopez on 7/30/24.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension FormatRule {
12 | /// Remove empty, non-conforming, extensions.
13 | static let emptyExtensions = FormatRule(
14 | help: "Remove empty, non-conforming, extensions.",
15 | disabledByDefault: true,
16 | orderAfter: [.unusedPrivateDeclarations]
17 | ) { formatter in
18 | var emptyExtensions = [TypeDeclaration]()
19 |
20 | for declaration in formatter.parseDeclarations() {
21 | guard declaration.keyword == "extension",
22 | let extensionDeclaration = declaration.asTypeDeclaration,
23 | extensionDeclaration.body.isEmpty,
24 | // Ensure that it is not a macro
25 | !extensionDeclaration.modifiers.contains(where: { $0.first == "@" })
26 | else { continue }
27 |
28 | // Ensure that the extension does not conform to any protocols
29 | guard extensionDeclaration.conformances.isEmpty else { continue }
30 |
31 | emptyExtensions.append(extensionDeclaration)
32 | }
33 |
34 | for emptyExtension in emptyExtensions {
35 | emptyExtension.remove()
36 | }
37 | } examples: {
38 | """
39 | ```diff
40 | - extension String {}
41 | -
42 | extension String: Equatable {}
43 | ```
44 | """
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Sources/Rules/FileMacro.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FileMacro.swift
3 | // SwiftFormat
4 | //
5 | // Created by Cal Stephens on 9/14/24.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension FormatRule {
12 | static let fileMacro = FormatRule(
13 | help: "Prefer either #file or #fileID, which have the same behavior in Swift 6 and later.",
14 | options: ["filemacro"]
15 | ) { formatter in
16 | // In the Swift 6 lanaguage mode and later, `#file` and `#fileID` have the same behavior.
17 | guard formatter.options.languageMode >= "6" else {
18 | return
19 | }
20 |
21 | if formatter.options.preferFileMacro {
22 | formatter.forEach(.keyword("#fileID")) { index, _ in
23 | formatter.replaceToken(at: index, with: .keyword("#file"))
24 | }
25 | } else {
26 | formatter.forEach(.keyword("#file")) { index, _ in
27 | formatter.replaceToken(at: index, with: .keyword("#fileID"))
28 | }
29 | }
30 | } examples: {
31 | """
32 | ```diff
33 | // --filemacro #file
34 | - func foo(file: StaticString = #fileID) { ... }
35 | + func foo(file: StaticString = #file) { ... }
36 |
37 | // --filemacro #fileID
38 | - func foo(file: StaticString = #file) { ... }
39 | + func foo(file: StaticString = #fileID) { ... }
40 | ```
41 | """
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Sources/Rules/HeaderFileName.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HeaderFileName.swift
3 | // SwiftFormat
4 | //
5 | // Created by Nick Lockwood on 5/3/23.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension FormatRule {
12 | /// Ensure file name reference in header matches actual file name
13 | static let headerFileName = FormatRule(
14 | help: "Ensure file name in header comment matches the actual file name.",
15 | runOnceOnly: true,
16 | orderAfter: [.fileHeader]
17 | ) { formatter in
18 | guard let fileName = formatter.options.fileInfo.fileName,
19 | let headerRange = formatter.headerCommentTokenRange(includingDirectives: ["*"]),
20 | fileName.hasSuffix(".swift")
21 | else {
22 | return
23 | }
24 |
25 | for i in headerRange {
26 | guard case let .commentBody(body) = formatter.tokens[i] else {
27 | continue
28 | }
29 | if body.hasSuffix(".swift"), body != fileName, !body.contains(where: { " /".contains($0) }) {
30 | formatter.replaceToken(at: i, with: .commentBody(fileName))
31 | }
32 | }
33 | } examples: {
34 | """
35 | For a file named `Bar.swift`:
36 |
37 | ```diff
38 | - // Foo.swift
39 | + // Bar.swift
40 | // SwiftFormat
41 | //
42 | // Created by Nick Lockwood on 5/3/23.
43 |
44 | struct Bar {}
45 | ```
46 | """
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Sources/Rules/HoistAwait.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HoistAwait.swift
3 | // SwiftFormat
4 | //
5 | // Created by Facundo Menzella on 2/9/23.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension FormatRule {
12 | /// Reposition `await` keyword outside of the current scope.
13 | static let hoistAwait = FormatRule(
14 | help: "Move inline `await` keyword(s) to start of expression.",
15 | options: ["asynccapturing"]
16 | ) { formatter in
17 | guard formatter.options.swiftVersion >= "5.5" else { return }
18 |
19 | formatter.forEachToken(where: {
20 | $0 == .startOfScope("(") || $0 == .startOfScope("[")
21 | }) { i, _ in
22 | formatter.hoistEffectKeyword("await", inScopeAt: i) { prevIndex in
23 | formatter.isSymbol(at: prevIndex, in: formatter.options.asyncCapturing)
24 | }
25 | }
26 | } examples: {
27 | """
28 | ```diff
29 | - greet(await forename, await surname)
30 | + await greet(forename, surname)
31 | ```
32 |
33 | ```diff
34 | - let foo = String(try await getFoo())
35 | + let foo = await String(try getFoo())
36 | ```
37 | """
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Sources/Rules/HoistTry.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HoistTry.swift
3 | // SwiftFormat
4 | //
5 | // Created by Facundo Menzella on 2/25/23.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension FormatRule {
12 | static let hoistTry = FormatRule(
13 | help: "Move inline `try` keyword(s) to start of expression.",
14 | options: ["throwcapturing"]
15 | ) { formatter in
16 | let names = formatter.options.throwCapturing.union(["expect"])
17 | formatter.forEachToken(where: {
18 | $0 == .startOfScope("(") || $0 == .startOfScope("[")
19 | }) { i, _ in
20 | formatter.hoistEffectKeyword("try", inScopeAt: i) { prevIndex in
21 | guard case let .identifier(name) = formatter.tokens[prevIndex] else {
22 | return false
23 | }
24 | return name.hasPrefix("XCTAssert") || formatter.isSymbol(at: prevIndex, in: names)
25 | }
26 | }
27 | } examples: {
28 | """
29 | ```diff
30 | - foo(try bar(), try baz())
31 | + try foo(bar(), baz())
32 | ```
33 |
34 | ```diff
35 | - let foo = String(try await getFoo())
36 | + let foo = try String(await getFoo())
37 | ```
38 | """
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Sources/Rules/LeadingDelimiters.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LeadingDelimiters.swift
3 | // SwiftFormat
4 | //
5 | // Created by Nick Lockwood on 3/11/19.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension FormatRule {
12 | static let leadingDelimiters = FormatRule(
13 | help: "Move leading delimiters to the end of the previous line.",
14 | sharedOptions: ["linebreaks"]
15 | ) { formatter in
16 | formatter.forEach(.delimiter) { i, _ in
17 | guard let endOfLine = formatter.index(of: .nonSpace, before: i, if: {
18 | $0.isLinebreak
19 | }) else {
20 | return
21 | }
22 | let nextIndex = formatter.index(of: .nonSpace, after: i) ?? (i + 1)
23 | formatter.insertSpace(formatter.currentIndentForLine(at: i), at: nextIndex)
24 | formatter.insertLinebreak(at: nextIndex)
25 | formatter.removeTokens(in: i + 1 ..< nextIndex)
26 | guard case .commentBody? = formatter.last(.nonSpace, before: endOfLine) else {
27 | formatter.removeTokens(in: endOfLine ..< i)
28 | return
29 | }
30 | let startIndex = formatter.index(of: .nonSpaceOrComment, before: endOfLine) ?? -1
31 | formatter.removeTokens(in: endOfLine ..< i)
32 | let comment = Array(formatter.tokens[startIndex + 1 ..< endOfLine])
33 | formatter.insert(comment, at: endOfLine + 1)
34 | formatter.removeTokens(in: startIndex + 1 ..< endOfLine)
35 | }
36 | } examples: {
37 | """
38 | ```diff
39 | - guard let foo = maybeFoo // first
40 | - , let bar = maybeBar else { ... }
41 |
42 | + guard let foo = maybeFoo, // first
43 | + let bar = maybeBar else { ... }
44 | ```
45 | """
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Sources/Rules/LinebreakAtEndOfFile.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LinebreakAtEndOfFile.swift
3 | // SwiftFormat
4 | //
5 | // Created by Nick Lockwood on 8/22/16.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension FormatRule {
12 | /// Always end file with a linebreak, to avoid incompatibility with certain unix tools:
13 | /// http://stackoverflow.com/questions/2287967/why-is-it-recommended-to-have-empty-line-in-the-end-of-file
14 | static let linebreakAtEndOfFile = FormatRule(
15 | help: "Add empty blank line at end of file.",
16 | sharedOptions: ["linebreaks"]
17 | ) { formatter in
18 | guard !formatter.options.fragment else { return }
19 | var wasLinebreak = true
20 | formatter.forEachToken(onlyWhereEnabled: false) { _, token in
21 | switch token {
22 | case .linebreak:
23 | wasLinebreak = true
24 | case .space:
25 | break
26 | default:
27 | wasLinebreak = false
28 | }
29 | }
30 | if formatter.isEnabled, !wasLinebreak {
31 | formatter.insertLinebreak(at: formatter.tokens.count)
32 | }
33 | } examples: {
34 | """
35 | ```diff
36 | struct Foo {↩
37 | let bar: Bar↩
38 | - }
39 | + }↩
40 | +
41 | ```
42 | """
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Sources/Rules/Linebreaks.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Linebreaks.swift
3 | // SwiftFormat
4 | //
5 | // Created by Nick Lockwood on 8/25/16.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension FormatRule {
12 | /// Standardise linebreak characters as whatever is specified in the options (\n by default)
13 | static let linebreaks = FormatRule(
14 | help: "Use specified linebreak character for all linebreaks (CR, LF or CRLF).",
15 | options: ["linebreaks"]
16 | ) { formatter in
17 | formatter.forEach(.linebreak) { i, _ in
18 | formatter.replaceToken(at: i, with: formatter.linebreakToken(for: i))
19 | }
20 | } examples: {
21 | nil
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Sources/Rules/PrivateStateVariables.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PrivateStateVariables.swift
3 | // SwiftFormatTests
4 | //
5 | // Created by Dave Paul on 9/13/24.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension FormatRule {
12 | static let privateStateVariables = FormatRule(
13 | help: "Adds `private` access control to @State properties without existing access control modifiers.",
14 | disabledByDefault: true
15 | ) { formatter in
16 | formatter.forEachToken(where: { $0 == .keyword("@State") || $0 == .keyword("@StateObject") }) { stateIndex, _ in
17 | guard let keywordIndex = formatter.index(after: stateIndex, where: {
18 | $0 == .keyword("let") || $0 == .keyword("var")
19 | }) else { return }
20 |
21 | // Don't override any existing access control:
22 | guard !formatter.tokens[stateIndex ..< keywordIndex].contains(where: {
23 | _FormatRules.aclModifiers.contains($0.string) || _FormatRules.aclSetterModifiers.contains($0.string)
24 | }) else {
25 | return
26 | }
27 |
28 | // Check for @Previewable - we won't modify @Previewable macros.
29 | guard !formatter.modifiersForDeclaration(at: keywordIndex, contains: "@Previewable") else {
30 | return
31 | }
32 |
33 | formatter.insert([.keyword("private"), .space(" ")], at: keywordIndex)
34 | }
35 | } examples: {
36 | """
37 | ```diff
38 | - @State var anInt: Int
39 | + @State private var anInt: Int
40 |
41 | - @StateObject var myInstance: MyObject
42 | + @StateObject private var myInstace: MyObject
43 | ```
44 | """
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Sources/Rules/RedundantBackticks.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RedundantBackticks.swift
3 | // SwiftFormat
4 | //
5 | // Created by Nick Lockwood on 3/7/17.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension FormatRule {
12 | /// Remove redundant backticks around non-keywords, or in places where keywords don't need escaping
13 | static let redundantBackticks = FormatRule(
14 | help: "Remove redundant backticks around identifiers."
15 | ) { formatter in
16 | formatter.forEach(.identifier) { i, token in
17 | guard token.string.first == "`", !formatter.backticksRequired(at: i) else {
18 | return
19 | }
20 | formatter.replaceToken(at: i, with: .identifier(token.unescaped()))
21 | }
22 | } examples: {
23 | """
24 | ```diff
25 | - let `infix` = bar
26 | + let infix = bar
27 | ```
28 |
29 | ```diff
30 | - func foo(with `default`: Int) {}
31 | + func foo(with default: Int) {}
32 | ```
33 | """
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Sources/Rules/RedundantBreak.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RedundantBreak.swift
3 | // SwiftFormat
4 | //
5 | // Created by Nick Lockwood on 1/23/19.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension FormatRule {
12 | /// Remove redundant `break` keyword from switch cases
13 | static let redundantBreak = FormatRule(
14 | help: "Remove redundant `break` in switch case."
15 | ) { formatter in
16 | formatter.forEach(.keyword("break")) { i, _ in
17 | guard formatter.last(.nonSpaceOrCommentOrLinebreak, before: i) != .startOfScope(":"),
18 | formatter.next(.nonSpaceOrCommentOrLinebreak, after: i)?.isEndOfScope == true,
19 | var startIndex = formatter.index(of: .nonSpace, before: i),
20 | let endIndex = formatter.index(of: .nonSpace, after: i),
21 | formatter.currentScope(at: i) == .startOfScope(":")
22 | else {
23 | return
24 | }
25 | if !formatter.tokens[startIndex].isLinebreak || !formatter.tokens[endIndex].isLinebreak {
26 | startIndex += 1
27 | }
28 | formatter.removeTokens(in: startIndex ..< endIndex)
29 | }
30 | } examples: {
31 | """
32 | ```diff
33 | switch foo {
34 | case bar:
35 | print("bar")
36 | - break
37 | default:
38 | print("default")
39 | - break
40 | }
41 | ```
42 | """
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Sources/Rules/RedundantExtensionACL.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RedundantExtensionACL.swift
3 | // SwiftFormat
4 | //
5 | // Created by Nick Lockwood on 2/3/19.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension FormatRule {
12 | /// Remove redundant access control level modifiers in extensions
13 | static let redundantExtensionACL = FormatRule(
14 | help: "Remove redundant access control modifiers."
15 | ) { formatter in
16 | formatter.forEach(.keyword("extension")) { i, _ in
17 | var acl = ""
18 | guard formatter.modifiersForDeclaration(at: i, contains: {
19 | acl = $1
20 | return _FormatRules.aclModifiers.contains(acl)
21 | }), let startIndex = formatter.index(of: .startOfScope("{"), after: i),
22 | var endIndex = formatter.index(of: .endOfScope("}"), after: startIndex) else {
23 | return
24 | }
25 | if acl == "private" { acl = "fileprivate" }
26 | while let aclIndex = formatter.lastIndex(of: .keyword(acl), in: startIndex + 1 ..< endIndex) {
27 | formatter.removeToken(at: aclIndex)
28 | if formatter.token(at: aclIndex)?.isSpace == true {
29 | formatter.removeToken(at: aclIndex)
30 | }
31 | endIndex = aclIndex
32 | }
33 | }
34 | } examples: {
35 | """
36 | ```diff
37 | public extension URL {
38 | - public func queryParameter(_ name: String) -> String { ... }
39 | }
40 |
41 | public extension URL {
42 | + func queryParameter(_ name: String) -> String { ... }
43 | }
44 | ```
45 | """
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Sources/Rules/RedundantGet.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RedundantGet.swift
3 | // SwiftFormat
4 | //
5 | // Created by Nick Lockwood on 11/15/16.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension FormatRule {
12 | /// Remove redundant `get {}` clause inside read-only computed property
13 | static let redundantGet = FormatRule(
14 | help: "Remove unneeded `get` clause inside computed properties."
15 | ) { formatter in
16 | formatter.forEach(.identifier("get")) { i, _ in
17 | if formatter.isAccessorKeyword(at: i, checkKeyword: false),
18 | let prevIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: i, if: {
19 | $0 == .startOfScope("{")
20 | }), let openIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i, if: {
21 | $0 == .startOfScope("{")
22 | }),
23 | let closeIndex = formatter.index(of: .endOfScope("}"), after: openIndex),
24 | let nextIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: closeIndex, if: {
25 | $0 == .endOfScope("}")
26 | })
27 | {
28 | formatter.removeTokens(in: closeIndex ..< nextIndex)
29 | formatter.removeTokens(in: prevIndex + 1 ... openIndex)
30 | // TODO: fix-up indenting of lines in between removed braces
31 | }
32 | }
33 | } examples: {
34 | """
35 | ```diff
36 | var foo: Int {
37 | - get {
38 | - return 5
39 | - }
40 | }
41 |
42 | var foo: Int {
43 | + return 5
44 | }
45 | ```
46 | """
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Sources/Rules/RedundantLetError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RedundantLetError.swift
3 | // SwiftFormat
4 | //
5 | // Created by Nick Lockwood on 12/16/18.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension FormatRule {
12 | /// Remove redundant `let error` from `catch` statements
13 | static let redundantLetError = FormatRule(
14 | help: "Remove redundant `let error` from `catch` clause."
15 |
16 | ) { formatter in
17 | formatter.forEach(.keyword("catch")) { i, _ in
18 | if let letIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i, if: {
19 | $0 == .keyword("let")
20 | }), let errorIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: letIndex, if: {
21 | $0 == .identifier("error")
22 | }), let scopeIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: errorIndex, if: {
23 | $0 == .startOfScope("{")
24 | }) {
25 | formatter.removeTokens(in: letIndex ..< scopeIndex)
26 | }
27 | }
28 | } examples: {
29 | """
30 | ```diff
31 | - do { ... } catch let error { log(error) }
32 | + do { ... } catch { log(error) }
33 | ```
34 | """
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Sources/Rules/RedundantStaticSelf.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RedundantStaticSelf.swift
3 | // SwiftFormat
4 | //
5 | // Created by Šimon Javora on 4/29/23.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension FormatRule {
12 | /// Remove redundant Self keyword
13 | static let redundantStaticSelf = FormatRule(
14 | help: "Remove explicit `Self` where applicable."
15 | ) { formatter in
16 | formatter.addOrRemoveSelf(static: true)
17 | } examples: {
18 | """
19 | ```diff
20 | enum Foo {
21 | static let bar = Bar()
22 |
23 | static func baaz() -> Bar {
24 | - Self.bar()
25 | + bar()
26 | }
27 | }
28 | ```
29 | """
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Sources/Rules/SortedImports.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SortedImports.swift
3 | // SwiftFormat
4 | //
5 | // Created by Pablo Carcelén on 11/22/17.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension FormatRule {
12 | /// Deprecated
13 | static let sortedImports = FormatRule(
14 | help: "Sort import statements alphabetically.",
15 | deprecationMessage: "Use sortImports instead.",
16 | options: ["importgrouping"],
17 | sharedOptions: ["linebreaks"]
18 | ) { formatter in
19 | _ = formatter.options.importGrouping
20 | _ = formatter.options.linebreak
21 | FormatRule.sortImports.apply(with: formatter)
22 | } examples: {
23 | nil
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Sources/Rules/SortedSwitchCases.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SortedSwitchCases.swift
3 | // SwiftFormat
4 | //
5 | // Created by Facundo Menzella on 9/22/20.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension FormatRule {
12 | /// Deprecated
13 | static let sortedSwitchCases = FormatRule(
14 | help: "Sort switch cases alphabetically.",
15 | deprecationMessage: "Use sortSwitchCases instead."
16 | ) { formatter in
17 | FormatRule.sortSwitchCases.apply(with: formatter)
18 | } examples: {
19 | nil
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Sources/Rules/SpaceAroundBraces.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SpaceAroundBraces.swift
3 | // SwiftFormat
4 | //
5 | // Created by Nick Lockwood on 8/22/16.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension FormatRule {
12 | /// Ensure that there is space between an opening brace and the preceding
13 | /// identifier, and between a closing brace and the following identifier.
14 | static let spaceAroundBraces = FormatRule(
15 | help: "Add or remove space around curly braces."
16 | ) { formatter in
17 | formatter.forEach(.startOfScope("{")) { i, _ in
18 | if let prevToken = formatter.token(at: i - 1) {
19 | switch prevToken {
20 | case .space, .linebreak, .operator(_, .prefix), .operator(_, .infix),
21 | .startOfScope where !prevToken.isStringDelimiter:
22 | break
23 | default:
24 | formatter.insert(.space(" "), at: i)
25 | }
26 | }
27 | }
28 | formatter.forEach(.endOfScope("}")) { i, _ in
29 | if let nextToken = formatter.token(at: i + 1) {
30 | switch nextToken {
31 | case .identifier, .keyword:
32 | formatter.insert(.space(" "), at: i + 1)
33 | default:
34 | break
35 | }
36 | }
37 | }
38 | } examples: {
39 | """
40 | ```diff
41 | - foo.filter{ return true }.map{ $0 }
42 | + foo.filter { return true }.map { $0 }
43 | ```
44 |
45 | ```diff
46 | - foo( {} )
47 | + foo({})
48 | ```
49 | """
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Sources/Rules/SpaceAroundGenerics.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SpaceAroundGenerics.swift
3 | // SwiftFormat
4 | //
5 | // Created by Nick Lockwood on 8/22/16.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension FormatRule {
12 | /// Ensure there is no space between an opening chevron and the preceding identifier
13 | static let spaceAroundGenerics = FormatRule(
14 | help: "Remove space around angle brackets."
15 | ) { formatter in
16 | formatter.forEach(.startOfScope("<")) { i, _ in
17 | if formatter.token(at: i - 1)?.isSpace == true,
18 | formatter.token(at: i - 2)?.isIdentifierOrKeyword == true
19 | {
20 | formatter.removeToken(at: i - 1)
21 | }
22 | }
23 | } examples: {
24 | """
25 | ```diff
26 | - Foo ()
27 | + Foo()
28 | ```
29 | """
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Sources/Rules/SpaceInsideBraces.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SpaceInsideBraces.swift
3 | // SwiftFormat
4 | //
5 | // Created by Nick Lockwood on 8/22/16.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension FormatRule {
12 | /// Ensure that there is space immediately inside braces
13 | static let spaceInsideBraces = FormatRule(
14 | help: "Add space inside curly braces."
15 | ) { formatter in
16 | formatter.forEach(.startOfScope("{")) { i, _ in
17 | if let nextToken = formatter.token(at: i + 1) {
18 | if !nextToken.isSpaceOrLinebreak,
19 | ![.endOfScope("}"), .startOfScope("{")].contains(nextToken)
20 | {
21 | formatter.insert(.space(" "), at: i + 1)
22 | }
23 | }
24 | }
25 | formatter.forEach(.endOfScope("}")) { i, _ in
26 | if let prevToken = formatter.token(at: i - 1) {
27 | if !prevToken.isSpaceOrLinebreak,
28 | ![.endOfScope("}"), .startOfScope("{")].contains(prevToken)
29 | {
30 | formatter.insert(.space(" "), at: i)
31 | }
32 | }
33 | }
34 | } examples: {
35 | """
36 | ```diff
37 | - foo.filter {return true}
38 | + foo.filter { return true }
39 | ```
40 | """
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Sources/Rules/SpaceInsideBrackets.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SpaceInsideBrackets.swift
3 | // SwiftFormat
4 | //
5 | // Created by Nick Lockwood on 8/22/16.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension FormatRule {
12 | /// Remove space immediately inside square brackets
13 | static let spaceInsideBrackets = FormatRule(
14 | help: "Remove space inside square brackets."
15 | ) { formatter in
16 | formatter.forEach(.startOfScope("[")) { i, _ in
17 | if formatter.token(at: i + 1)?.isSpace == true,
18 | formatter.token(at: i + 2)?.isComment == false
19 | {
20 | formatter.removeToken(at: i + 1)
21 | }
22 | }
23 | formatter.forEach(.endOfScope("]")) { i, _ in
24 | if formatter.token(at: i - 1)?.isSpace == true,
25 | formatter.token(at: i - 2)?.isCommentOrLinebreak == false
26 | {
27 | formatter.removeToken(at: i - 1)
28 | }
29 | }
30 | } examples: {
31 | """
32 | ```diff
33 | - [ 1, 2, 3 ]
34 | + [1, 2, 3]
35 | ```
36 | """
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Sources/Rules/SpaceInsideGenerics.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SpaceInsideGenerics.swift
3 | // SwiftFormat
4 | //
5 | // Created by Nick Lockwood on 8/22/16.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension FormatRule {
12 | /// Remove space immediately inside chevrons
13 | static let spaceInsideGenerics = FormatRule(
14 | help: "Remove space inside angle brackets."
15 | ) { formatter in
16 | formatter.forEach(.startOfScope("<")) { i, _ in
17 | if formatter.token(at: i + 1)?.isSpace == true {
18 | formatter.removeToken(at: i + 1)
19 | }
20 | }
21 | formatter.forEach(.endOfScope(">")) { i, _ in
22 | if formatter.token(at: i - 1)?.isSpace == true,
23 | formatter.token(at: i - 2)?.isLinebreak == false
24 | {
25 | formatter.removeToken(at: i - 1)
26 | }
27 | }
28 | } examples: {
29 | """
30 | ```diff
31 | - Foo< Bar, Baz >
32 | + Foo
33 | ```
34 | """
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Sources/Rules/SpaceInsideParens.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SpaceInsideParens.swift
3 | // SwiftFormat
4 | //
5 | // Created by Nick Lockwood on 8/22/16.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension FormatRule {
12 | /// Remove space immediately inside parens
13 | static let spaceInsideParens = FormatRule(
14 | help: "Remove space inside parentheses."
15 | ) { formatter in
16 | formatter.forEach(.startOfScope("(")) { i, _ in
17 | if formatter.token(at: i + 1)?.isSpace == true,
18 | formatter.token(at: i + 2)?.isComment == false
19 | {
20 | formatter.removeToken(at: i + 1)
21 | }
22 | }
23 | formatter.forEach(.endOfScope(")")) { i, _ in
24 | if formatter.token(at: i - 1)?.isSpace == true,
25 | formatter.token(at: i - 2)?.isCommentOrLinebreak == false
26 | {
27 | formatter.removeToken(at: i - 1)
28 | }
29 | }
30 | } examples: {
31 | """
32 | ```diff
33 | - ( a, b)
34 | + (a, b)
35 | ```
36 | """
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Sources/Rules/Specifiers.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Specifiers.swift
3 | // SwiftFormat
4 | //
5 | // Created by Nick Lockwood on 9/6/16.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension FormatRule {
12 | /// Deprecated
13 | static let specifiers = FormatRule(
14 | help: "Use consistent ordering for member modifiers.",
15 | deprecationMessage: "Use modifierOrder instead.",
16 | options: ["modifierorder"]
17 | ) { formatter in
18 | _ = formatter.options.modifierOrder
19 | FormatRule.modifierOrder.apply(with: formatter)
20 | } examples: {
21 | nil
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Sources/Rules/StrongOutlets.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StrongOutlets.swift
3 | // SwiftFormat
4 | //
5 | // Created by Nick Lockwood on 11/24/17.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension FormatRule {
12 | /// Strip unnecessary `weak` from @IBOutlet properties (except delegates and datasources)
13 | static let strongOutlets = FormatRule(
14 | help: "Remove `weak` modifier from `@IBOutlet` properties."
15 | ) { formatter in
16 | formatter.forEach(.keyword("@IBOutlet")) { i, _ in
17 | guard let varIndex = formatter.index(of: .keyword("var"), after: i),
18 | let weakIndex = (i ..< varIndex).first(where: { formatter.tokens[$0] == .identifier("weak") }),
19 | case let .identifier(name)? = formatter.next(.identifier, after: varIndex)
20 | else {
21 | return
22 | }
23 | let lowercased = name.lowercased()
24 | if lowercased.hasSuffix("delegate") || lowercased.hasSuffix("datasource") {
25 | return
26 | }
27 | if formatter.tokens[weakIndex + 1].isSpace {
28 | formatter.removeToken(at: weakIndex + 1)
29 | } else if formatter.tokens[weakIndex - 1].isSpace {
30 | formatter.removeToken(at: weakIndex - 1)
31 | }
32 | formatter.removeToken(at: weakIndex)
33 | }
34 | } examples: {
35 | """
36 | As per Apple's recommendation
37 | (https://developer.apple.com/videos/play/wwdc2015/407/ @ 32:30).
38 |
39 | ```diff
40 | - @IBOutlet weak var label: UILabel!
41 | + @IBOutlet var label: UILabel!
42 | ```
43 | """
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Sources/Rules/StrongifiedSelf.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StrongifiedSelf.swift
3 | // SwiftFormat
4 | //
5 | // Created by Nick Lockwood on 1/24/19.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension FormatRule {
12 | /// Removed backticks from `self` when strongifying
13 | static let strongifiedSelf = FormatRule(
14 | help: "Remove backticks around `self` in Optional unwrap expressions."
15 | ) { formatter in
16 | formatter.forEach(.identifier("`self`")) { i, _ in
17 | guard formatter.options.swiftVersion >= "4.2",
18 | let equalIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i, if: {
19 | $0 == .operator("=", .infix)
20 | }), formatter.next(.nonSpaceOrCommentOrLinebreak, after: equalIndex) == .identifier("self"),
21 | formatter.isConditionalStatement(at: i)
22 | else {
23 | return
24 | }
25 | formatter.replaceToken(at: i, with: .identifier("self"))
26 | }
27 | } examples: {
28 | """
29 | ```diff
30 | - guard let `self` = self else { return }
31 | + guard let self = self else { return }
32 | ```
33 |
34 | **NOTE:** assignment to un-escaped `self` is only supported in Swift 4.2 and
35 | above, so the `strongifiedSelf` rule is disabled unless the Swift version is
36 | set to 4.2 or above.
37 | """
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Sources/Rules/SwiftTestingTestCaseNames.swift:
--------------------------------------------------------------------------------
1 | // Created by Cal Stephens on 2/19/25.
2 | // Copyright © 2025 Airbnb Inc. All rights reserved.
3 |
4 | public extension FormatRule {
5 | static let swiftTestingTestCaseNames = FormatRule(
6 | help: "In Swift Testing, don't prefix @Test methods with 'test'."
7 | ) { formatter in
8 | guard formatter.hasImport("Testing") else { return }
9 |
10 | formatter.forEach(.keyword("func")) { funcKeywordIndex, _ in
11 | if formatter.modifiersForDeclaration(at: funcKeywordIndex, contains: "@Test") {
12 | formatter.removeTestPrefix(fromFunctionAt: funcKeywordIndex)
13 | }
14 | }
15 | } examples: {
16 | """
17 | ```diff
18 | import Testing
19 |
20 | struct MyFeatureTests {
21 | - @Test func testMyFeatureHasNoBugs() {
22 | + @Test func myFeatureHasNoBugs() {
23 | let myFeature = MyFeature()
24 | myFeature.runAction()
25 | #expect(!myFeature.hasBugs, "My feature has no bugs")
26 | #expect(myFeature.crashes.isEmpty, "My feature doesn't crash")
27 | #expect(myFeature.crashReport == nil)
28 | }
29 | }
30 | ```
31 | """
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Sources/Rules/TrailingSpace.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TrailingSpace.swift
3 | // SwiftFormat
4 | //
5 | // Created by Nick Lockwood on 11/24/16.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension FormatRule {
12 | /// Remove trailing space from the end of lines, as it has no semantic
13 | /// meaning and leads to noise in commits.
14 | static let trailingSpace = FormatRule(
15 | help: "Remove trailing space at end of a line.",
16 | orderAfter: [.wrap, .wrapArguments],
17 | options: ["trimwhitespace"]
18 | ) { formatter in
19 | formatter.forEach(.space) { i, _ in
20 | if formatter.token(at: i + 1)?.isLinebreak ?? true,
21 | formatter.options.truncateBlankLines || formatter.token(at: i - 1)?.isLinebreak == false
22 | {
23 | formatter.removeToken(at: i)
24 | }
25 | }
26 | } examples: {
27 | """
28 | ```diff
29 | - let foo: Foo␣
30 | + let foo: Foo
31 | - ␣␣␣␣
32 | +
33 | - func bar() {␣␣
34 | + func bar() {
35 | ␣␣␣␣print("foo")
36 | }
37 | ```
38 | """
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Sources/Rules/WrapConditionalBodies.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WrapConditionalBodies.swift
3 | // SwiftFormat
4 | //
5 | // Created by Nick Lockwood on 11/6/21.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension FormatRule {
12 | static let wrapConditionalBodies = FormatRule(
13 | help: "Wrap the bodies of inline conditional statements onto a new line.",
14 | disabledByDefault: true,
15 | sharedOptions: ["linebreaks", "indent"]
16 | ) { formatter in
17 | formatter.forEachToken(where: { [.keyword("if"), .keyword("else")].contains($0) }) { i, _ in
18 | guard let startIndex = formatter.index(of: .startOfScope("{"), after: i) else {
19 | return formatter.fatalError("Expected {", at: i)
20 | }
21 | formatter.wrapStatementBody(at: startIndex)
22 | }
23 | } examples: {
24 | """
25 | ```diff
26 | - guard let foo = bar else { return baz }
27 | + guard let foo = bar else {
28 | + return baz
29 | + }
30 | ```
31 |
32 | ```diff
33 | - if foo { return bar }
34 | + if foo {
35 | + return bar
36 | + }
37 | ```
38 | """
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Sources/Rules/WrapLoopBodies.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WrapLoopBodies.swift
3 | // SwiftFormat
4 | //
5 | // Created by Nick Lockwood on 1/3/24.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension FormatRule {
12 | static let wrapLoopBodies = FormatRule(
13 | help: "Wrap the bodies of inline loop statements onto a new line.",
14 | orderAfter: [.preferForLoop],
15 | sharedOptions: ["linebreaks", "indent"]
16 | ) { formatter in
17 | formatter.forEachToken(where: { [
18 | .keyword("for"),
19 | .keyword("while"),
20 | .keyword("repeat"),
21 | ].contains($0) }) { i, token in
22 | if let startIndex = formatter.index(of: .startOfScope("{"), after: i) {
23 | formatter.wrapStatementBody(at: startIndex)
24 | } else if token == .keyword("for") {
25 | return formatter.fatalError("Expected {", at: i)
26 | }
27 | }
28 | } examples: {
29 | """
30 | ```diff
31 | - for foo in array { print(foo) }
32 | + for foo in array {
33 | + print(foo)
34 | + }
35 | ```
36 |
37 | ```diff
38 | - while let foo = bar.next() { print(foo) }
39 | + while let foo = bar.next() {
40 | + print(foo)
41 | + }
42 | ```
43 | """
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Sources/Rules/WrapSwitchCases.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WrapSwitchCases.swift
3 | // SwiftFormat
4 | //
5 | // Created by Nick Lockwood on 8/28/20.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension FormatRule {
12 | /// Writes one switch case per line
13 | static let wrapSwitchCases = FormatRule(
14 | help: "Wrap comma-delimited switch cases onto multiple lines.",
15 | disabledByDefault: true,
16 | sharedOptions: ["linebreaks", "tabwidth", "indent", "smarttabs"]
17 | ) { formatter in
18 | formatter.forEach(.endOfScope("case")) { i, _ in
19 | guard var endIndex = formatter.index(of: .startOfScope(":"), after: i) else { return }
20 | let lineStart = formatter.startOfLine(at: i)
21 | let indent = formatter.spaceEquivalentToTokens(from: lineStart, upTo: i + 2)
22 |
23 | var startIndex = i
24 | while let commaIndex = formatter.index(of: .delimiter(","), in: startIndex + 1 ..< endIndex),
25 | let nextIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: commaIndex)
26 | {
27 | if formatter.index(of: .linebreak, in: commaIndex ..< nextIndex) == nil {
28 | formatter.insertLinebreak(at: commaIndex + 1)
29 | let delta = formatter.insertSpace(indent, at: commaIndex + 2)
30 | endIndex += 1 + delta
31 | }
32 | startIndex = commaIndex
33 | }
34 | }
35 | } examples: {
36 | """
37 | ```diff
38 | switch foo {
39 | - case .bar, .baz:
40 | break
41 | }
42 |
43 | switch foo {
44 | + case .foo,
45 | + .bar:
46 | break
47 | }
48 | ```
49 | """
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Sources/SwiftFormat.h:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftFormat.h
3 | // SwiftFormat
4 | //
5 | // Created by Nick Lockwood on 12/08/2016.
6 | // Copyright 2016 Nick Lockwood
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/nicklockwood/SwiftFormat
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | #import
33 |
34 | //! Project version number for SwiftFormat.
35 | FOUNDATION_EXPORT double SwiftFormatVersionNumber;
36 |
37 | //! Project version string for SwiftFormat.
38 | FOUNDATION_EXPORT const unsigned char SwiftFormatVersionString[];
39 |
--------------------------------------------------------------------------------
/SwiftFormat.podspec.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "SwiftFormat",
3 | "version": "0.56.2",
4 | "license": {
5 | "type": "MIT",
6 | "file": "LICENSE.md"
7 | },
8 | "summary": "Mac and iOS library for formatting Swift source code.",
9 | "homepage": "https://github.com/nicklockwood/SwiftFormat",
10 | "authors": "Nick Lockwood",
11 | "source": {
12 | "git": "https://github.com/nicklockwood/SwiftFormat.git",
13 | "tag": "0.56.2"
14 | },
15 | "default_subspecs": "Core",
16 | "subspecs": [
17 | {
18 | "name": "Core",
19 | "source_files": "Sources/**/*.swift"
20 | },
21 | {
22 | "name": "CLI",
23 | "preserve_paths": "CommandLineTool/swiftformat",
24 | "platforms": {
25 | "ios": "11.0",
26 | "tvos": "11.0",
27 | "osx": "10.14"
28 | }
29 | }
30 | ],
31 | "platforms": {
32 | "ios": "11.0",
33 | "tvos": "11.0",
34 | "osx": "10.14"
35 | },
36 | "swift_versions": [
37 | "5.3", "5.4", "5.5", "5.6", "5.7", "5.8", "5.9", "5.10"
38 | ],
39 | "requires_arc": true
40 | }
41 |
--------------------------------------------------------------------------------
/SwiftFormat.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/SwiftFormat.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/SwiftFormat.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded
6 |
7 | PreviewsEnabled
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Tests/BadConfig/.swiftformat:
--------------------------------------------------------------------------------
1 | --disable ifdef
--------------------------------------------------------------------------------
/Tests/BadConfig/Test.swift:
--------------------------------------------------------------------------------
1 | //Badly formatted code
2 | func
3 | foo (bar :Int){}
4 |
--------------------------------------------------------------------------------
/Tests/GlobTest[5].txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicklockwood/SwiftFormat/1d02d0f54a5123c3ef67084b318f4421427b7a51/Tests/GlobTest[5].txt
--------------------------------------------------------------------------------
/Tests/ProjectFilePaths.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ProjectFilePaths.swift
3 | // SwiftFormatTests
4 | //
5 | // Created by Cal Stephens on 8/3/24.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | @testable import SwiftFormat
11 |
12 | let projectDirectory = URL(fileURLWithPath: #file)
13 | .deletingLastPathComponent().deletingLastPathComponent()
14 |
15 | let projectURL = projectDirectory
16 | .appendingPathComponent("SwiftFormat.xcodeproj")
17 | .appendingPathComponent("project.pbxproj")
18 |
19 | let allSourceFiles = allSwiftFiles(inDirectory: "Sources")
20 | let allRuleFiles = allSwiftFiles(inDirectory: "Sources/Rules")
21 |
22 | let allTestFiles = allSwiftFiles(inDirectory: "Tests")
23 | let allRuleTestFiles = allSwiftFiles(inDirectory: "Tests/Rules")
24 |
25 | let changeLogURL =
26 | projectDirectory.appendingPathComponent("CHANGELOG.md")
27 |
28 | let podspecURL =
29 | projectDirectory.appendingPathComponent("SwiftFormat.podspec.json")
30 |
31 | let rulesURL =
32 | projectDirectory.appendingPathComponent("Rules.md")
33 |
34 | let rulesFile =
35 | try! String(contentsOf: rulesURL, encoding: .utf8)
36 |
37 | let ruleRegistryURL =
38 | projectDirectory.appendingPathComponent("Sources/RuleRegistry.generated.swift")
39 |
40 | private func allSwiftFiles(inDirectory directory: String) -> [URL] {
41 | var swiftFiles: [URL] = []
42 | let directory = projectDirectory.appendingPathComponent(directory)
43 | let errors = enumerateFiles(withInputURL: directory) { fileURL, _, _ in
44 | {
45 | guard fileURL.pathExtension == "swift" else { return }
46 | swiftFiles.append(fileURL)
47 | }
48 | }
49 | assert(errors.isEmpty, "Encountered errors accessing files in \(directory): \(errors)")
50 | assert(!swiftFiles.isEmpty, "Could not load files in \(directory)")
51 | return swiftFiles
52 | }
53 |
--------------------------------------------------------------------------------
/Tests/Rules/ApplicationMainTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ApplicationMainTests.swift
3 | // SwiftFormatTests
4 | //
5 | // Created by Nick Lockwood on 5/20/23.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import SwiftFormat
11 |
12 | class ApplicationMainTests: XCTestCase {
13 | func testUIApplicationMainReplacedByMain() {
14 | let input = """
15 | @UIApplicationMain
16 | class AppDelegate: UIResponder, UIApplicationDelegate {}
17 | """
18 | let output = """
19 | @main
20 | class AppDelegate: UIResponder, UIApplicationDelegate {}
21 | """
22 | let options = FormatOptions(swiftVersion: "5.3")
23 | testFormatting(for: input, output, rule: .applicationMain, options: options)
24 | }
25 |
26 | func testNSApplicationMainReplacedByMain() {
27 | let input = """
28 | @NSApplicationMain
29 | class AppDelegate: NSObject, NSApplicationDelegate {}
30 | """
31 | let output = """
32 | @main
33 | class AppDelegate: NSObject, NSApplicationDelegate {}
34 | """
35 | let options = FormatOptions(swiftVersion: "5.3")
36 | testFormatting(for: input, output, rule: .applicationMain, options: options)
37 | }
38 |
39 | func testNSApplicationMainNotReplacedInSwift5_2() {
40 | let input = """
41 | @NSApplicationMain
42 | class AppDelegate: NSObject, NSApplicationDelegate {}
43 | """
44 | let options = FormatOptions(swiftVersion: "5.2")
45 | testFormatting(for: input, rule: .applicationMain, options: options)
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Tests/Rules/BlankLinesBetweenChainedFunctionsTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BlankLinesBetweenChainedFunctionsTests.swift
3 | // SwiftFormatTests
4 | //
5 | // Created by Nick Lockwood on 7/28/23.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import SwiftFormat
11 |
12 | class BlankLinesBetweenChainedFunctionsTests: XCTestCase {
13 | func testBlankLinesBetweenChainedFunctions() {
14 | let input = """
15 | [0, 1, 2]
16 | .map { $0 * 2 }
17 |
18 |
19 |
20 | .map { $0 * 3 }
21 | """
22 | let output1 = """
23 | [0, 1, 2]
24 | .map { $0 * 2 }
25 | .map { $0 * 3 }
26 | """
27 | let output2 = """
28 | [0, 1, 2]
29 | .map { $0 * 2 }
30 | .map { $0 * 3 }
31 | """
32 | testFormatting(for: input, [output1, output2], rules: [.blankLinesBetweenChainedFunctions])
33 | }
34 |
35 | func testBlankLinesWithCommentsBetweenChainedFunctions() {
36 | let input = """
37 | [0, 1, 2]
38 | .map { $0 * 2 }
39 |
40 | // Multiplies by 3
41 |
42 | .map { $0 * 3 }
43 | """
44 | let output = """
45 | [0, 1, 2]
46 | .map { $0 * 2 }
47 | // Multiplies by 3
48 | .map { $0 * 3 }
49 | """
50 | testFormatting(for: input, output, rule: .blankLinesBetweenChainedFunctions)
51 | }
52 |
53 | func testBlankLinesWithMarkCommentBetweenChainedFunctions() {
54 | let input = """
55 | [0, 1, 2]
56 | .map { $0 * 2 }
57 |
58 | // MARK: hello
59 |
60 | .map { $0 * 3 }
61 | """
62 | testFormatting(for: input, rules: [.blankLinesBetweenChainedFunctions, .blankLinesAroundMark])
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/Tests/Rules/FileMacroTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FileMacroTests.swift
3 | // SwiftFormat
4 | //
5 | // Created by Cal Stephens on 9/14/24.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import SwiftFormat
11 |
12 | final class FileMacroTests: XCTestCase {
13 | func testPreservesFileMacroInSwift5Mode() {
14 | let input = """
15 | func foo(file: StaticString = #fileID) {
16 | print(file)
17 | }
18 |
19 | func bar(file: StaticString = #file) {
20 | print(file)
21 | }
22 | """
23 |
24 | let options = FormatOptions(languageMode: "5")
25 | testFormatting(for: input, rule: .fileMacro, options: options)
26 | }
27 |
28 | func testUpdatesFileIDInSwift6Mode() {
29 | let input = """
30 | func foo(file: StaticString = #fileID) {
31 | print(file)
32 | }
33 | """
34 |
35 | let output = """
36 | func foo(file: StaticString = #file) {
37 | print(file)
38 | }
39 | """
40 |
41 | let options = FormatOptions(preferFileMacro: true, languageMode: "6")
42 | testFormatting(for: input, output, rule: .fileMacro, options: options)
43 | }
44 |
45 | func testPreferFileID() {
46 | let input = """
47 | func foo(file: StaticString = #file) {
48 | print(file)
49 | }
50 | """
51 |
52 | let output = """
53 | func foo(file: StaticString = #fileID) {
54 | print(file)
55 | }
56 | """
57 |
58 | let options = FormatOptions(preferFileMacro: false, languageMode: "6")
59 | testFormatting(for: input, output, rule: .fileMacro, options: options)
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/Tests/Rules/HeaderFileNameTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HeaderFileNameTests.swift
3 | // SwiftFormatTests
4 | //
5 | // Created by Nick Lockwood on 5/3/23.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import SwiftFormat
11 |
12 | class HeaderFileNameTests: XCTestCase {
13 | func testHeaderFileNameReplaced() {
14 | let input = """
15 | // MyFile.swift
16 |
17 | let foo = bar
18 | """
19 | let output = """
20 | // YourFile.swift
21 |
22 | let foo = bar
23 | """
24 | let options = FormatOptions(fileInfo: FileInfo(filePath: "~/YourFile.swift"))
25 | testFormatting(for: input, output, rule: .headerFileName, options: options)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Tests/Rules/LeadingDelimitersTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LeadingDelimitersTests.swift
3 | // SwiftFormatTests
4 | //
5 | // Created by Nick Lockwood on 3/11/19.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import SwiftFormat
11 |
12 | class LeadingDelimitersTests: XCTestCase {
13 | func testLeadingCommaMovedToPreviousLine() {
14 | let input = """
15 | let foo = 5
16 | , bar = 6
17 | """
18 | let output = """
19 | let foo = 5,
20 | bar = 6
21 | """
22 | testFormatting(for: input, output, rule: .leadingDelimiters)
23 | }
24 |
25 | func testLeadingColonFollowedByCommentMovedToPreviousLine() {
26 | let input = """
27 | let foo
28 | : /* string */ String
29 | """
30 | let output = """
31 | let foo:
32 | /* string */ String
33 | """
34 | testFormatting(for: input, output, rule: .leadingDelimiters)
35 | }
36 |
37 | func testCommaMovedBeforeCommentIfLineEndsInComment() {
38 | let input = """
39 | let foo = 5 // first
40 | , bar = 6
41 | """
42 | let output = """
43 | let foo = 5, // first
44 | bar = 6
45 | """
46 | testFormatting(for: input, output, rule: .leadingDelimiters)
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Tests/Rules/LinebreakAtEndOfFileTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LinebreakAtEndOfFileTests.swift
3 | // SwiftFormatTests
4 | //
5 | // Created by Nick Lockwood on 8/22/16.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import SwiftFormat
11 |
12 | class LinebreakAtEndOfFileTests: XCTestCase {
13 | func testLinebreakAtEndOfFile() {
14 | let input = "foo\nbar"
15 | let output = "foo\nbar\n"
16 | testFormatting(for: input, output, rule: .linebreakAtEndOfFile)
17 | }
18 |
19 | func testNoLinebreakAtEndOfFragment() {
20 | let input = "foo\nbar"
21 | let options = FormatOptions(fragment: true)
22 | testFormatting(for: input, rule: .linebreakAtEndOfFile, options: options)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Tests/Rules/LinebreaksTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LinebreaksTests.swift
3 | // SwiftFormatTests
4 | //
5 | // Created by Nick Lockwood on 8/25/16.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import SwiftFormat
11 |
12 | class LinebreaksTests: XCTestCase {
13 | func testCarriageReturn() {
14 | let input = "foo\rbar"
15 | let output = "foo\nbar"
16 | testFormatting(for: input, output, rule: .linebreaks)
17 | }
18 |
19 | func testCarriageReturnLinefeed() {
20 | let input = "foo\r\nbar"
21 | let output = "foo\nbar"
22 | testFormatting(for: input, output, rule: .linebreaks)
23 | }
24 |
25 | func testVerticalTab() {
26 | let input = "foo\u{000B}bar"
27 | let output = "foo\nbar"
28 | testFormatting(for: input, output, rule: .linebreaks)
29 | }
30 |
31 | func testFormfeed() {
32 | let input = "foo\u{000C}bar"
33 | let output = "foo\nbar"
34 | testFormatting(for: input, output, rule: .linebreaks)
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Tests/Rules/RedundantExtensionACLTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RedundantExtensionACLTests.swift
3 | // SwiftFormatTests
4 | //
5 | // Created by Nick Lockwood on 2/3/19.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import SwiftFormat
11 |
12 | class RedundantExtensionACLTests: XCTestCase {
13 | func testPublicExtensionMemberACLStripped() {
14 | let input = """
15 | public extension Foo {
16 | public var bar: Int { 5 }
17 | private static let baz = "baz"
18 | public func quux() {}
19 | }
20 | """
21 | let output = """
22 | public extension Foo {
23 | var bar: Int { 5 }
24 | private static let baz = "baz"
25 | func quux() {}
26 | }
27 | """
28 | testFormatting(for: input, output, rule: .redundantExtensionACL)
29 | }
30 |
31 | func testPrivateExtensionMemberACLNotStrippedUnlessFileprivate() {
32 | let input = """
33 | private extension Foo {
34 | fileprivate var bar: Int { 5 }
35 | private static let baz = "baz"
36 | fileprivate func quux() {}
37 | }
38 | """
39 | let output = """
40 | private extension Foo {
41 | var bar: Int { 5 }
42 | private static let baz = "baz"
43 | func quux() {}
44 | }
45 | """
46 | testFormatting(for: input, output, rule: .redundantExtensionACL)
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Tests/Rules/RedundantLetErrorTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RedundantLetErrorTests.swift
3 | // SwiftFormatTests
4 | //
5 | // Created by Nick Lockwood on 12/16/18.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import SwiftFormat
11 |
12 | class RedundantLetErrorTests: XCTestCase {
13 | func testCatchLetError() {
14 | let input = "do {} catch let error {}"
15 | let output = "do {} catch {}"
16 | testFormatting(for: input, output, rule: .redundantLetError)
17 | }
18 |
19 | func testCatchLetErrorWithTypedThrows() {
20 | let input = "do throws(Foo) {} catch let error {}"
21 | let output = "do throws(Foo) {} catch {}"
22 | testFormatting(for: input, output, rule: .redundantLetError)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Tests/Rules/RedundantRawValuesTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RedundantRawValuesTests.swift
3 | // SwiftFormatTests
4 | //
5 | // Created by Nick Lockwood on 12/22/16.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import SwiftFormat
11 |
12 | class RedundantRawValuesTests: XCTestCase {
13 | func testRemoveRedundantRawString() {
14 | let input = "enum Foo: String {\n case bar = \"bar\"\n case baz = \"baz\"\n}"
15 | let output = "enum Foo: String {\n case bar\n case baz\n}"
16 | testFormatting(for: input, output, rule: .redundantRawValues)
17 | }
18 |
19 | func testRemoveCommaDelimitedCaseRawStringCases() {
20 | let input = "enum Foo: String { case bar = \"bar\", baz = \"baz\" }"
21 | let output = "enum Foo: String { case bar, baz }"
22 | testFormatting(for: input, output, rule: .redundantRawValues,
23 | exclude: [.wrapEnumCases])
24 | }
25 |
26 | func testRemoveBacktickCaseRawStringCases() {
27 | let input = "enum Foo: String { case `as` = \"as\", `let` = \"let\" }"
28 | let output = "enum Foo: String { case `as`, `let` }"
29 | testFormatting(for: input, output, rule: .redundantRawValues,
30 | exclude: [.wrapEnumCases])
31 | }
32 |
33 | func testNoRemoveRawStringIfNameDoesntMatch() {
34 | let input = "enum Foo: String {\n case bar = \"foo\"\n}"
35 | testFormatting(for: input, rule: .redundantRawValues)
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Tests/Rules/RedundantTypedThrowsTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RedundantTypedThrowsTests.swift
3 | // SwiftFormatTests
4 | //
5 | // Created by Miguel Jimenez on 6/8/24.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import SwiftFormat
11 |
12 | class RedundantTypedThrowsTests: XCTestCase {
13 | func testRemovesRedundantNeverTypeThrows() {
14 | let input = """
15 | func foo() throws(Never) -> Int {
16 | 0
17 | }
18 | """
19 |
20 | let output = """
21 | func foo() -> Int {
22 | 0
23 | }
24 | """
25 |
26 | let options = FormatOptions(swiftVersion: "6.0")
27 | testFormatting(for: input, output, rule: .redundantTypedThrows, options: options)
28 | }
29 |
30 | func testRemovesRedundantAnyErrorTypeThrows() {
31 | let input = """
32 | func foo() throws(any Error) -> Int {
33 | throw MyError.foo
34 | }
35 | """
36 |
37 | let output = """
38 | func foo() throws -> Int {
39 | throw MyError.foo
40 | }
41 | """
42 |
43 | let options = FormatOptions(swiftVersion: "6.0")
44 | testFormatting(for: input, output, rule: .redundantTypedThrows, options: options)
45 | }
46 |
47 | func testDontRemovesNonRedundantErrorTypeThrows() {
48 | let input = """
49 | func bar() throws(BarError) -> Foo {
50 | throw .foo
51 | }
52 |
53 | func foo() throws(Error) -> Int {
54 | throw MyError.foo
55 | }
56 | """
57 |
58 | let options = FormatOptions(swiftVersion: "6.0")
59 | testFormatting(for: input, rule: .redundantTypedThrows, options: options)
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/Tests/Rules/SpaceAroundCommentsTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SpaceAroundCommentsTests.swift
3 | // SwiftFormatTests
4 | //
5 | // Created by Nick Lockwood on 8/31/16.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import SwiftFormat
11 |
12 | class SpaceAroundCommentsTests: XCTestCase {
13 | func testSpaceAroundCommentInParens() {
14 | let input = "(/* foo */)"
15 | let output = "( /* foo */ )"
16 | testFormatting(for: input, output, rule: .spaceAroundComments,
17 | exclude: [.redundantParens])
18 | }
19 |
20 | func testNoSpaceAroundCommentAtStartAndEndOfFile() {
21 | let input = "/* foo */"
22 | testFormatting(for: input, rule: .spaceAroundComments)
23 | }
24 |
25 | func testNoSpaceAroundCommentBeforeComma() {
26 | let input = "(foo /* foo */ , bar)"
27 | let output = "(foo /* foo */, bar)"
28 | testFormatting(for: input, output, rule: .spaceAroundComments)
29 | }
30 |
31 | func testSpaceAroundSingleLineComment() {
32 | let input = "func foo() {// comment\n}"
33 | let output = "func foo() { // comment\n}"
34 | testFormatting(for: input, output, rule: .spaceAroundComments)
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Tests/Rules/SpaceAroundGenericsTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SpaceAroundGenericsTests.swift
3 | // SwiftFormatTests
4 | //
5 | // Created by Nick Lockwood on 8/22/16.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import SwiftFormat
11 |
12 | class SpaceAroundGenericsTests: XCTestCase {
13 | func testSpaceAroundGenerics() {
14 | let input = "Foo >"
15 | let output = "Foo>"
16 | testFormatting(for: input, output, rule: .spaceAroundGenerics)
17 | }
18 |
19 | func testSpaceAroundGenericsFollowedByAndOperator() {
20 | let input = "if foo is Foo && baz {}"
21 | testFormatting(for: input, rule: .spaceAroundGenerics, exclude: [.andOperator])
22 | }
23 |
24 | func testSpaceAroundGenericResultBuilder() {
25 | let input = "func foo(@SomeResultBuilder builder: () -> Void) {}"
26 | testFormatting(for: input, rule: .spaceAroundGenerics)
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Tests/Rules/SpaceInsideBracesTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SpaceInsideBracesTests.swift
3 | // SwiftFormatTests
4 | //
5 | // Created by Nick Lockwood on 8/22/16.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import SwiftFormat
11 |
12 | class SpaceInsideBracesTests: XCTestCase {
13 | func testSpaceInsideBraces() {
14 | let input = "foo({bar})"
15 | let output = "foo({ bar })"
16 | testFormatting(for: input, output, rule: .spaceInsideBraces, exclude: [.trailingClosures])
17 | }
18 |
19 | func testNoExtraSpaceInsidebraces() {
20 | let input = "{ foo }"
21 | testFormatting(for: input, rule: .spaceInsideBraces, exclude: [.trailingClosures])
22 | }
23 |
24 | func testNoSpaceAddedInsideEmptybraces() {
25 | let input = "foo({})"
26 | testFormatting(for: input, rule: .spaceInsideBraces, exclude: [.trailingClosures])
27 | }
28 |
29 | func testNoSpaceAddedBetweenDoublebraces() {
30 | let input = "func foo() -> () -> Void {{ bar() }}"
31 | testFormatting(for: input, rule: .spaceInsideBraces)
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Tests/Rules/SpaceInsideBracketsTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SpaceInsideBracketsTests.swift
3 | // SwiftFormatTests
4 | //
5 | // Created by Nick Lockwood on 8/22/16.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import SwiftFormat
11 |
12 | class SpaceInsideBracketsTests: XCTestCase {
13 | func testSpaceInsideBrackets() {
14 | let input = "foo[ 5 ]"
15 | let output = "foo[5]"
16 | testFormatting(for: input, output, rule: .spaceInsideBrackets)
17 | }
18 |
19 | func testSpaceInsideWrappedArray() {
20 | let input = "[ foo,\n bar ]"
21 | let output = "[foo,\n bar]"
22 | let options = FormatOptions(wrapCollections: .disabled)
23 | testFormatting(for: input, output, rule: .spaceInsideBrackets, options: options)
24 | }
25 |
26 | func testSpaceBeforeCommentInsideWrappedArray() {
27 | let input = "[ // foo\n bar,\n]"
28 | let options = FormatOptions(wrapCollections: .disabled)
29 | testFormatting(for: input, rule: .spaceInsideBrackets, options: options)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Tests/Rules/SpaceInsideGenericsTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SpaceInsideGenericsTests.swift
3 | // SwiftFormatTests
4 | //
5 | // Created by Nick Lockwood on 8/22/16.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import SwiftFormat
11 |
12 | class SpaceInsideGenericsTests: XCTestCase {
13 | func testSpaceInsideGenerics() {
14 | let input = "Foo< Bar< Baz > >"
15 | let output = "Foo>"
16 | testFormatting(for: input, output, rule: .spaceInsideGenerics)
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Tests/Rules/SpaceInsideParensTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SpaceInsideParensTests.swift
3 | // SwiftFormatTests
4 | //
5 | // Created by Nick Lockwood on 8/22/16.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import SwiftFormat
11 |
12 | class SpaceInsideParensTests: XCTestCase {
13 | func testSpaceInsideParens() {
14 | let input = "( 1, ( 2, 3 ) )"
15 | let output = "(1, (2, 3))"
16 | testFormatting(for: input, output, rule: .spaceInsideParens)
17 | }
18 |
19 | func testSpaceBeforeCommentInsideParens() {
20 | let input = "( /* foo */ 1, 2 )"
21 | let output = "( /* foo */ 1, 2)"
22 | testFormatting(for: input, output, rule: .spaceInsideParens)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Tests/Rules/WrapLoopBodiesTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WrapLoopBodiesTests.swift
3 | // SwiftFormatTests
4 | //
5 | // Created by Nick Lockwood on 1/3/24.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import SwiftFormat
11 |
12 | class WrapLoopBodiesTests: XCTestCase {
13 | func testWrapForLoop() {
14 | let input = "for foo in bar { print(foo) }"
15 | let output = """
16 | for foo in bar {
17 | print(foo)
18 | }
19 | """
20 | testFormatting(for: input, output, rule: .wrapLoopBodies)
21 | }
22 |
23 | func testWrapWhileLoop() {
24 | let input = "while let foo = bar.next() { print(foo) }"
25 | let output = """
26 | while let foo = bar.next() {
27 | print(foo)
28 | }
29 | """
30 | testFormatting(for: input, output, rule: .wrapLoopBodies)
31 | }
32 |
33 | func testWrapRepeatWhileLoop() {
34 | let input = "repeat { print(foo) } while condition()"
35 | let output = """
36 | repeat {
37 | print(foo)
38 | } while condition()
39 | """
40 | testFormatting(for: input, output, rule: .wrapLoopBodies)
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Tests/Rules/WrapSwitchCasesTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WrapSwitchCasesTests.swift
3 | // SwiftFormatTests
4 | //
5 | // Created by Nick Lockwood on 8/28/20.
6 | // Copyright © 2024 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import SwiftFormat
11 |
12 | class WrapSwitchCasesTests: XCTestCase {
13 | func testMultilineSwitchCases() {
14 | let input = """
15 | func foo() {
16 | switch bar {
17 | case .a(_), .b, "c":
18 | print("")
19 | case .d:
20 | print("")
21 | }
22 | }
23 | """
24 | let output = """
25 | func foo() {
26 | switch bar {
27 | case .a(_),
28 | .b,
29 | "c":
30 | print("")
31 | case .d:
32 | print("")
33 | }
34 | }
35 | """
36 | testFormatting(for: input, output, rule: .wrapSwitchCases)
37 | }
38 |
39 | func testIfAfterSwitchCaseNotWrapped() {
40 | let input = """
41 | switch foo {
42 | case "foo":
43 | print("")
44 | default:
45 | print("")
46 | }
47 | if let foo = bar, foo != .baz {
48 | throw error
49 | }
50 | """
51 | testFormatting(for: input, rule: .wrapSwitchCases)
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Tests/VersionTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // VersionTests.swift
3 | // SwiftFormat
4 | //
5 | // Created by Nick Lockwood on 28/01/2019.
6 | // Copyright © 2019 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import SwiftFormat
11 |
12 | class VersionTests: XCTestCase {
13 | // MARK: Version parsing
14 |
15 | func testParseEmptyVersion() throws {
16 | let version = Version(rawValue: "")
17 | XCTAssertNil(version)
18 | }
19 |
20 | func testParseOrdinaryVersion() throws {
21 | let version = Version(rawValue: "4.2")
22 | XCTAssertEqual(version, "4.2")
23 | }
24 |
25 | func testParsePaddedVersion() throws {
26 | let version = Version(rawValue: " 4.2 ")
27 | XCTAssertEqual(version, "4.2")
28 | }
29 |
30 | func testParseThreePartVersion() throws {
31 | let version = Version(rawValue: "3.1.5")
32 | XCTAssertNotNil(version)
33 | XCTAssertEqual(version, "3.1.5")
34 | }
35 |
36 | func testParsePreviewVersion() throws {
37 | let version = Version(rawValue: "3.0-PREVIEW-4")
38 | XCTAssertNotNil(version)
39 | XCTAssertEqual(version, "3.0-PREVIEW-4")
40 | }
41 |
42 | func testComparison() throws {
43 | let version = Version(rawValue: "3.1.5")
44 | XCTAssertLessThan(version ?? "0", "3.2")
45 | XCTAssertGreaterThan(version ?? "0", "3.1.4")
46 | }
47 |
48 | func testPreviewComparison() throws {
49 | let version = Version(rawValue: "3.0-PREVIEW-4")
50 | XCTAssertLessThan(version ?? "0", "4.0")
51 | XCTAssertGreaterThan(version ?? "0", "2.0")
52 | }
53 |
54 | func testWildcardVersion() throws {
55 | let version = Version(rawValue: "3.x")
56 | XCTAssertNotNil(version)
57 | XCTAssertLessThan(version ?? "0", "4.0")
58 | XCTAssertGreaterThan(version ?? "0", "2.0")
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/format.sh:
--------------------------------------------------------------------------------
1 | if [[ -z "${TRAVIS}" ]]; then
2 | CommandLineTool/swiftformat . --cache ignore
3 | fi
4 |
--------------------------------------------------------------------------------