mirror of
https://github.com/nicklockwood/SwiftFormat.git
synced 2026-05-17 10:30:35 +00:00
835f6f287d
Co-authored-by: calda <1811727+calda@users.noreply.github.com>
4541 lines
101 KiB
Swift
4541 lines
101 KiB
Swift
//
|
|
// OrganizeDeclarationsTests.swift
|
|
// SwiftFormatTests
|
|
//
|
|
// Created by Cal Stephens on 8/16/20.
|
|
// Copyright © 2024 Nick Lockwood. All rights reserved.
|
|
//
|
|
|
|
import XCTest
|
|
@testable import SwiftFormat
|
|
|
|
final class OrganizeDeclarationsTests: XCTestCase {
|
|
func testOrganizeClassDeclarationsIntoCategories() {
|
|
let input = """
|
|
public class Foo {
|
|
private func privateMethod() {}
|
|
|
|
private let bar = 1
|
|
public let baz = 1
|
|
open var quack = 2
|
|
package func packageMethod() {}
|
|
var quux = 2
|
|
|
|
/// `open` is the only visibility keyword that
|
|
/// can also be used as an identifier.
|
|
var open = 10
|
|
|
|
/*
|
|
* Block comment
|
|
*/
|
|
|
|
init() {}
|
|
|
|
/// Doc comment
|
|
public func publicMethod() {}
|
|
|
|
#if DEBUG
|
|
private var foo: Foo? { nil }
|
|
#endif
|
|
}
|
|
|
|
enum Bar {
|
|
private var bar: Bar { Bar() }
|
|
case enumCase
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
public class Foo {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
/*
|
|
* Block comment
|
|
*/
|
|
|
|
init() {}
|
|
|
|
// MARK: Open
|
|
|
|
open var quack = 2
|
|
|
|
// MARK: Public
|
|
|
|
public let baz = 1
|
|
|
|
/// Doc comment
|
|
public func publicMethod() {}
|
|
|
|
// MARK: Package
|
|
|
|
package func packageMethod() {}
|
|
|
|
// MARK: Internal
|
|
|
|
var quux = 2
|
|
|
|
/// `open` is the only visibility keyword that
|
|
/// can also be used as an identifier.
|
|
var open = 10
|
|
|
|
// MARK: Private
|
|
|
|
private let bar = 1
|
|
|
|
#if DEBUG
|
|
private var foo: Foo? { nil }
|
|
#endif
|
|
|
|
private func privateMethod() {}
|
|
|
|
}
|
|
|
|
enum Bar {
|
|
case enumCase
|
|
|
|
// MARK: Private
|
|
|
|
private var bar: Bar { Bar() }
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope, .wrapPropertyBodies]
|
|
)
|
|
}
|
|
|
|
func testOrganizeClassDeclarationsIntoCategoriesWithCustomTypeOrder() {
|
|
let input = """
|
|
public class Foo {
|
|
private func privateMethod() {}
|
|
|
|
private let bar = 1
|
|
public let baz = 1
|
|
open var quack = 2
|
|
package func packageMethod() {}
|
|
var quux = 2
|
|
|
|
/// `open` is the only visibility keyword that
|
|
/// can also be used as an identifier.
|
|
var open = 10
|
|
|
|
/*
|
|
* Block comment
|
|
*/
|
|
|
|
init() {}
|
|
|
|
/// Doc comment
|
|
public func publicMethod() {}
|
|
|
|
#if DEBUG
|
|
private var foo: Foo? { nil }
|
|
#endif
|
|
}
|
|
|
|
enum Bar {
|
|
private var bar: Bar { Bar() }
|
|
case enumCase
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
public class Foo {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
/*
|
|
* Block comment
|
|
*/
|
|
|
|
init() {}
|
|
|
|
// MARK: Open
|
|
|
|
open var quack = 2
|
|
|
|
// MARK: Public
|
|
|
|
public let baz = 1
|
|
|
|
/// Doc comment
|
|
public func publicMethod() {}
|
|
|
|
// MARK: Package
|
|
|
|
package func packageMethod() {}
|
|
|
|
// MARK: Internal
|
|
|
|
var quux = 2
|
|
|
|
/// `open` is the only visibility keyword that
|
|
/// can also be used as an identifier.
|
|
var open = 10
|
|
|
|
// MARK: Private
|
|
|
|
private let bar = 1
|
|
|
|
#if DEBUG
|
|
private var foo: Foo? { nil }
|
|
#endif
|
|
|
|
private func privateMethod() {}
|
|
|
|
}
|
|
|
|
enum Bar {
|
|
case enumCase
|
|
|
|
// MARK: Private
|
|
|
|
private var bar: Bar { Bar() }
|
|
}
|
|
"""
|
|
|
|
// The configuration used in Airbnb's Swift Style Guide,
|
|
// as defined here: https://github.com/airbnb/swift#subsection-organization
|
|
let airbnbVisibilityOrder = """
|
|
beforeMarks,instanceLifecycle,open,public,package,internal,private,fileprivate
|
|
"""
|
|
let airbnbTypeOrder = """
|
|
nestedType,staticProperty,staticPropertyWithBody,classPropertyWithBody,instanceProperty,instancePropertyWithBody,staticMethod,classMethod,instanceMethod
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
options: FormatOptions(
|
|
visibilityOrder: airbnbVisibilityOrder.components(separatedBy: ","),
|
|
typeOrder: airbnbTypeOrder.components(separatedBy: ",")
|
|
),
|
|
exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope, .wrapPropertyBodies]
|
|
)
|
|
}
|
|
|
|
func testOrganizeClassDeclarationsIntoCategoriesInTypeOrder() {
|
|
let input = """
|
|
public class Foo {
|
|
private func privateMethod() {}
|
|
|
|
private let bar = 1
|
|
public let baz = 1
|
|
open var quack = 2
|
|
package func packageMethod() {}
|
|
var quux = 2
|
|
|
|
/// `open` is the only visibility keyword that
|
|
/// can also be used as an identifier.
|
|
var open = 10
|
|
|
|
/*
|
|
* Block comment
|
|
*/
|
|
|
|
init() {}
|
|
|
|
/// Doc comment
|
|
public func publicMethod() {}
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
public class Foo {
|
|
|
|
// MARK: Properties
|
|
|
|
open var quack = 2
|
|
|
|
public let baz = 1
|
|
|
|
var quux = 2
|
|
|
|
/// `open` is the only visibility keyword that
|
|
/// can also be used as an identifier.
|
|
var open = 10
|
|
|
|
private let bar = 1
|
|
|
|
// MARK: Lifecycle
|
|
|
|
/*
|
|
* Block comment
|
|
*/
|
|
|
|
init() {}
|
|
|
|
// MARK: Functions
|
|
|
|
/// Doc comment
|
|
public func publicMethod() {}
|
|
|
|
package func packageMethod() {}
|
|
|
|
private func privateMethod() {}
|
|
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
options: FormatOptions(categoryMarkComment: "MARK: %c", organizationMode: .type),
|
|
exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope]
|
|
)
|
|
}
|
|
|
|
func testOrganizeTypeWithOverridenFieldsInVisibilityOrder() {
|
|
let input = """
|
|
class Test {
|
|
|
|
override var b: Any? { nil }
|
|
|
|
var a = ""
|
|
|
|
override func bar() -> Bar {
|
|
Bar()
|
|
}
|
|
|
|
func foo() -> Foo {
|
|
Foo()
|
|
}
|
|
|
|
func baaz() -> Baaz {
|
|
Baaz()
|
|
}
|
|
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, rule: .organizeDeclarations,
|
|
exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope, .sortImports, .wrapPropertyBodies]
|
|
)
|
|
}
|
|
|
|
func testOrganizeTypeWithOverridenFieldsInTypeOrder() {
|
|
let input = """
|
|
class Test {
|
|
|
|
var a = ""
|
|
|
|
override var b: Any? { nil }
|
|
|
|
func foo() -> Foo {
|
|
Foo()
|
|
}
|
|
|
|
override func bar() -> Bar {
|
|
Bar()
|
|
}
|
|
|
|
func baaz() -> Baaz {
|
|
Baaz()
|
|
}
|
|
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
class Test {
|
|
|
|
// MARK: Overridden Properties
|
|
|
|
override var b: Any? { nil }
|
|
|
|
// MARK: Properties
|
|
|
|
var a = ""
|
|
|
|
// MARK: Overridden Functions
|
|
|
|
override func bar() -> Bar {
|
|
Bar()
|
|
}
|
|
|
|
// MARK: Functions
|
|
|
|
func foo() -> Foo {
|
|
Foo()
|
|
}
|
|
|
|
func baaz() -> Baaz {
|
|
Baaz()
|
|
}
|
|
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
options: FormatOptions(organizationMode: .type),
|
|
exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope, .sortImports, .wrapPropertyBodies]
|
|
)
|
|
}
|
|
|
|
func testOrganizeTypeWithSwiftUIMethodInVisibilityOrder() {
|
|
let input = """
|
|
class Test {
|
|
|
|
func bar() -> some View {
|
|
EmptyView()
|
|
}
|
|
|
|
func foo() -> Foo {
|
|
Foo()
|
|
}
|
|
|
|
func baaz() -> Baaz {
|
|
Baaz()
|
|
}
|
|
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, rule: .organizeDeclarations,
|
|
exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope, .sortImports]
|
|
)
|
|
}
|
|
|
|
func testOrganizeSwiftUIViewInTypeOrder() {
|
|
let input = """
|
|
struct ContentView: View {
|
|
|
|
private var label: String
|
|
|
|
@State
|
|
var isOn: Bool = false
|
|
|
|
@ViewBuilder
|
|
private var toggle: some View {
|
|
Toggle(label, isOn: $isOn)
|
|
.fixedSize()
|
|
}
|
|
|
|
init(label: String) {
|
|
self.label = label
|
|
}
|
|
|
|
@ViewBuilder
|
|
var body: some View {
|
|
toggle
|
|
}
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
struct ContentView: View {
|
|
|
|
// MARK: SwiftUI Properties
|
|
|
|
@State
|
|
var isOn: Bool = false
|
|
|
|
// MARK: Properties
|
|
|
|
private var label: String
|
|
|
|
// MARK: Lifecycle
|
|
|
|
init(label: String) {
|
|
self.label = label
|
|
}
|
|
|
|
// MARK: Content Properties
|
|
|
|
@ViewBuilder
|
|
var body: some View {
|
|
toggle
|
|
}
|
|
|
|
@ViewBuilder
|
|
private var toggle: some View {
|
|
Toggle(label, isOn: $isOn)
|
|
.fixedSize()
|
|
}
|
|
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
options: FormatOptions(categoryMarkComment: "MARK: %c", organizationMode: .type),
|
|
exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope, .privateStateVariables, .redundantViewBuilder]
|
|
)
|
|
}
|
|
|
|
func testOrganizeSwiftUIViewModifierInTypeOrder() {
|
|
let input = """
|
|
struct Modifier: ViewModifier {
|
|
|
|
private var label: String
|
|
|
|
@State
|
|
var isOn: Bool = false
|
|
|
|
@ViewBuilder
|
|
private var toggle: some View {
|
|
Toggle(label, isOn: $isOn)
|
|
.fixedSize()
|
|
}
|
|
|
|
func body(content: Content) -> some View {
|
|
content
|
|
.overlay {
|
|
toggle
|
|
}
|
|
}
|
|
|
|
init(label: String) {
|
|
self.label = label
|
|
}
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
struct Modifier: ViewModifier {
|
|
|
|
// MARK: SwiftUI Properties
|
|
|
|
@State
|
|
var isOn: Bool = false
|
|
|
|
// MARK: Properties
|
|
|
|
private var label: String
|
|
|
|
// MARK: Lifecycle
|
|
|
|
init(label: String) {
|
|
self.label = label
|
|
}
|
|
|
|
// MARK: Content Properties
|
|
|
|
@ViewBuilder
|
|
private var toggle: some View {
|
|
Toggle(label, isOn: $isOn)
|
|
.fixedSize()
|
|
}
|
|
|
|
// MARK: Content Methods
|
|
|
|
func body(content: Content) -> some View {
|
|
content
|
|
.overlay {
|
|
toggle
|
|
}
|
|
}
|
|
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
options: FormatOptions(categoryMarkComment: "MARK: %c", organizationMode: .type),
|
|
exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope, .privateStateVariables, .redundantViewBuilder]
|
|
)
|
|
}
|
|
|
|
func testCustomOrganizationInVisibilityOrder() {
|
|
let input = """
|
|
public class Foo {
|
|
public func bar() {}
|
|
func baz() {}
|
|
private func quux() {}
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
public class Foo {
|
|
|
|
// MARK: Private
|
|
|
|
private func quux() {}
|
|
|
|
// MARK: Internal
|
|
|
|
func baz() {}
|
|
|
|
// MARK: Public
|
|
|
|
public func bar() {}
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
options: FormatOptions(
|
|
visibilityOrder: ["private", "internal", "public"],
|
|
typeOrder: DeclarationType.allCases.map(\.rawValue)
|
|
),
|
|
exclude: [.blankLinesAtStartOfScope, .privateStateVariables]
|
|
)
|
|
}
|
|
|
|
func testCustomOrganizationInVisibilityOrderWithParametrizedTypeOrder() {
|
|
let input = """
|
|
public class Foo {
|
|
|
|
// MARK: Private
|
|
|
|
private func quux() {}
|
|
|
|
// MARK: Internal
|
|
|
|
var baaz: Baaz
|
|
|
|
func baz() {}
|
|
|
|
// MARK: Public
|
|
|
|
public func bar() {}
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
public class Foo {
|
|
|
|
// MARK: Private
|
|
|
|
private func quux() {}
|
|
|
|
// MARK: Internal
|
|
|
|
func baz() {}
|
|
|
|
var baaz: Baaz
|
|
|
|
// MARK: Public
|
|
|
|
public func bar() {}
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
options: FormatOptions(
|
|
visibilityOrder: ["private", "internal", "public"],
|
|
typeOrder: ["beforeMarks", "nestedType", "instanceLifecycle", "instanceMethod", "instanceProperty"]
|
|
),
|
|
exclude: [.blankLinesAtStartOfScope]
|
|
)
|
|
}
|
|
|
|
func testCustomOrganizationInTypeOrder() {
|
|
let input = """
|
|
public class Foo {
|
|
private func quux() {}
|
|
var baaz: Baaz
|
|
func baz() {}
|
|
init()
|
|
override public func baar()
|
|
public func bar() {}
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
public class Foo {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
init()
|
|
|
|
// MARK: Functions
|
|
|
|
public func bar() {}
|
|
|
|
func baz() {}
|
|
|
|
private func quux() {}
|
|
|
|
// MARK: Properties
|
|
|
|
var baaz: Baaz
|
|
|
|
// MARK: Overridden Functions
|
|
|
|
override public func baar()
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
options: FormatOptions(
|
|
organizationMode: .type,
|
|
typeOrder: ["beforeMarks", "instanceLifecycle", "instanceMethod", "nestedType", "instanceProperty", "overriddenMethod"]
|
|
),
|
|
exclude: [.blankLinesAtStartOfScope]
|
|
)
|
|
}
|
|
|
|
func testOrganizeDeclarationsIgnoresNotDefinedCategories() {
|
|
let input = """
|
|
public class Foo {
|
|
private func quux() {}
|
|
var baaz: Baaz
|
|
func baz() {}
|
|
init()
|
|
override public func baar()
|
|
public func bar() {}
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
public class Foo {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
init()
|
|
|
|
// MARK: Functions
|
|
|
|
override public func baar()
|
|
public func bar() {}
|
|
|
|
func baz() {}
|
|
|
|
private func quux() {}
|
|
|
|
// MARK: Properties
|
|
|
|
var baaz: Baaz
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
options: FormatOptions(
|
|
organizationMode: .type,
|
|
typeOrder: ["beforeMarks", "nestedType", "instanceLifecycle", "instanceMethod", "instanceProperty"]
|
|
),
|
|
exclude: [.blankLinesAtStartOfScope]
|
|
)
|
|
}
|
|
|
|
func testCustomOrganizationInTypeOrderWithParametrizedVisibilityOrder() {
|
|
let input = """
|
|
public class Foo {
|
|
private func quux() {}
|
|
var baaz: Baaz
|
|
private var fooo: Fooo
|
|
func baz() {}
|
|
init()
|
|
override public func baar()
|
|
public func bar() {}
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
public class Foo {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
init()
|
|
|
|
// MARK: Functions
|
|
|
|
private func quux() {}
|
|
|
|
func baz() {}
|
|
|
|
public func bar() {}
|
|
|
|
// MARK: Properties
|
|
|
|
private var fooo: Fooo
|
|
|
|
var baaz: Baaz
|
|
|
|
// MARK: Overridden Functions
|
|
|
|
override public func baar()
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
options: FormatOptions(
|
|
organizationMode: .type,
|
|
visibilityOrder: ["private", "internal", "public"],
|
|
typeOrder: ["beforeMarks", "nestedType", "instanceLifecycle", "instanceMethod", "instanceProperty", "overriddenMethod"]
|
|
),
|
|
exclude: [.blankLinesAtStartOfScope]
|
|
)
|
|
}
|
|
|
|
func testCustomDeclarationTypeUsedAsTopLevelCategory() {
|
|
let input = """
|
|
class Test {
|
|
private let foo = "foo"
|
|
func bar() {}
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
class Test {
|
|
|
|
// MARK: Functions
|
|
|
|
func bar() {}
|
|
|
|
// MARK: Private
|
|
|
|
private let foo = "foo"
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
options: FormatOptions(
|
|
organizationMode: .visibility,
|
|
visibilityOrder: ["instanceMethod"] + Visibility.allCases.map(\.rawValue),
|
|
typeOrder: DeclarationType.allCases.map(\.rawValue).filter { $0 != "instanceMethod" }
|
|
),
|
|
exclude: [.blankLinesAtStartOfScope]
|
|
)
|
|
}
|
|
|
|
func testVisibilityModeWithoutInstanceLifecycle() {
|
|
let input = """
|
|
class Test {
|
|
init() {}
|
|
private func bar() {}
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
class Test {
|
|
|
|
// MARK: Internal
|
|
|
|
init() {}
|
|
|
|
// MARK: Private
|
|
|
|
private func bar() {}
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
options: FormatOptions(
|
|
organizationMode: .visibility,
|
|
visibilityOrder: Visibility.allCases.map(\.rawValue),
|
|
typeOrder: DeclarationType.allCases.map(\.rawValue)
|
|
),
|
|
exclude: [.blankLinesAtStartOfScope]
|
|
)
|
|
}
|
|
|
|
func testCustomCategoryNamesInVisibilityOrder() {
|
|
let input = """
|
|
public class Foo {
|
|
public var bar: Bar
|
|
init(bar: Bar) {
|
|
self.bar = bar
|
|
}
|
|
func baaz() {}
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
public class Foo {
|
|
|
|
// MARK: Init
|
|
|
|
init(bar: Bar) {
|
|
self.bar = bar
|
|
}
|
|
|
|
// MARK: Public_Group
|
|
|
|
public var bar: Bar
|
|
|
|
// MARK: Internal
|
|
|
|
func baaz() {}
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
options: FormatOptions(
|
|
organizationMode: .visibility,
|
|
customVisibilityMarks: ["instanceLifecycle:Init", "public:Public_Group"]
|
|
),
|
|
exclude: [.blankLinesAtStartOfScope]
|
|
)
|
|
}
|
|
|
|
func testCustomCategoryNamesInTypeOrder() {
|
|
let input = """
|
|
public class Foo {
|
|
public var bar: Bar
|
|
init(bar: Bar) {
|
|
self.bar = bar
|
|
}
|
|
func baaz() {}
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
public class Foo {
|
|
|
|
// MARK: Bar_Bar
|
|
|
|
public var bar: Bar
|
|
|
|
// MARK: Init
|
|
|
|
init(bar: Bar) {
|
|
self.bar = bar
|
|
}
|
|
|
|
// MARK: Buuuz Lightyeeeaaar
|
|
|
|
func baaz() {}
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
options: FormatOptions(
|
|
organizationMode: .type,
|
|
customTypeMarks: ["instanceLifecycle:Init", "instanceProperty:Bar_Bar", "instanceMethod:Buuuz Lightyeeeaaar"]
|
|
),
|
|
exclude: [.blankLinesAtStartOfScope]
|
|
)
|
|
}
|
|
|
|
func testClassNestedInClassIsOrganized() {
|
|
let input = """
|
|
public class Foo {
|
|
public class Bar {
|
|
fileprivate func baz() {}
|
|
public var quux: Int
|
|
init() {}
|
|
deinit {}
|
|
}
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
public class Foo {
|
|
public class Bar {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
init() {}
|
|
deinit {}
|
|
|
|
// MARK: Public
|
|
|
|
public var quux: Int
|
|
|
|
// MARK: Fileprivate
|
|
|
|
fileprivate func baz() {}
|
|
}
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
exclude: [.blankLinesAtStartOfScope, .enumNamespaces]
|
|
)
|
|
}
|
|
|
|
func testStructNestedInExtensionIsOrganized() {
|
|
let input = """
|
|
public extension Foo {
|
|
struct Bar {
|
|
private var foo: Int
|
|
private let bar: Int
|
|
|
|
public var foobar: (Int, Int) {
|
|
(foo, bar)
|
|
}
|
|
|
|
public init(foo: Int, bar: Int) {
|
|
self.foo = foo
|
|
self.bar = bar
|
|
}
|
|
}
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
public extension Foo {
|
|
struct Bar {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
public init(foo: Int, bar: Int) {
|
|
self.foo = foo
|
|
self.bar = bar
|
|
}
|
|
|
|
// MARK: Public
|
|
|
|
public var foobar: (Int, Int) {
|
|
(foo, bar)
|
|
}
|
|
|
|
// MARK: Private
|
|
|
|
private var foo: Int
|
|
private let bar: Int
|
|
|
|
}
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope]
|
|
)
|
|
}
|
|
|
|
func testOrganizePrivateSet() {
|
|
let input = """
|
|
public class Foo {
|
|
public private(set) var bar: Int
|
|
private(set) var baz: Int
|
|
internal private(set) var baz: Int
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
public class Foo {
|
|
|
|
// MARK: Public
|
|
|
|
public private(set) var bar: Int
|
|
|
|
// MARK: Internal
|
|
|
|
private(set) var baz: Int
|
|
internal private(set) var baz: Int
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
exclude: [.blankLinesAtStartOfScope, .redundantInternal]
|
|
)
|
|
}
|
|
|
|
func testSortDeclarationTypes() {
|
|
let input = """
|
|
class Foo {
|
|
static var a1: Int = 1
|
|
static var a2: Int = 2
|
|
var d1: CGFloat {
|
|
3.141592653589
|
|
}
|
|
|
|
class var b2: String {
|
|
"class computed property"
|
|
}
|
|
|
|
func g() -> Int {
|
|
10
|
|
}
|
|
|
|
let c: String = String {
|
|
"closure body"
|
|
}()
|
|
|
|
static func e() {}
|
|
|
|
typealias Bar = Int
|
|
|
|
static var b1: String {
|
|
"static computed property"
|
|
}
|
|
|
|
class func f() -> Foo {
|
|
Foo()
|
|
}
|
|
|
|
enum NestedEnum {}
|
|
|
|
var d2: CGFloat = 3.141592653589 {
|
|
didSet {}
|
|
}
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
class Foo {
|
|
typealias Bar = Int
|
|
|
|
enum NestedEnum {}
|
|
|
|
static var a1: Int = 1
|
|
static var a2: Int = 2
|
|
|
|
static var b1: String {
|
|
"static computed property"
|
|
}
|
|
|
|
class var b2: String {
|
|
"class computed property"
|
|
}
|
|
|
|
let c: String = String {
|
|
"closure body"
|
|
}()
|
|
|
|
var d1: CGFloat {
|
|
3.141592653589
|
|
}
|
|
|
|
var d2: CGFloat = 3.141592653589 {
|
|
didSet {}
|
|
}
|
|
|
|
static func e() {}
|
|
|
|
class func f() -> Foo {
|
|
Foo()
|
|
}
|
|
|
|
func g() -> Int {
|
|
10
|
|
}
|
|
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
exclude: [.blankLinesAtEndOfScope, .redundantType, .redundantClosure]
|
|
)
|
|
}
|
|
|
|
func testSortDeclarationTypesByType() {
|
|
let input = """
|
|
class Foo {
|
|
var a: Int
|
|
init(a: Int) {
|
|
self.a = a
|
|
}
|
|
private convenience init() {
|
|
self.init(a: 0)
|
|
}
|
|
|
|
static var a1: Int = 1
|
|
static var a2: Int = 2
|
|
var d1: CGFloat {
|
|
3.141592653589
|
|
}
|
|
|
|
class var b2: String {
|
|
"class computed property"
|
|
}
|
|
|
|
func g() -> Int {
|
|
10
|
|
}
|
|
|
|
let c: String = String {
|
|
"closure body"
|
|
}()
|
|
|
|
static func e() {}
|
|
|
|
typealias Bar = Int
|
|
|
|
static var b1: String {
|
|
"static computed property"
|
|
}
|
|
|
|
class func f() -> Foo {
|
|
Foo()
|
|
}
|
|
|
|
enum NestedEnum {}
|
|
|
|
var d2: CGFloat = 3.141592653589 {
|
|
didSet {}
|
|
}
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
class Foo {
|
|
|
|
// MARK: Nested Types
|
|
|
|
typealias Bar = Int
|
|
|
|
enum NestedEnum {}
|
|
|
|
// MARK: Static Properties
|
|
|
|
static var a1: Int = 1
|
|
static var a2: Int = 2
|
|
|
|
// MARK: Static Computed Properties
|
|
|
|
static var b1: String {
|
|
"static computed property"
|
|
}
|
|
|
|
// MARK: Class Properties
|
|
|
|
class var b2: String {
|
|
"class computed property"
|
|
}
|
|
|
|
// MARK: Properties
|
|
|
|
var a: Int
|
|
let c: String = String {
|
|
"closure body"
|
|
}()
|
|
|
|
var d2: CGFloat = 3.141592653589 {
|
|
didSet {}
|
|
}
|
|
|
|
// MARK: Computed Properties
|
|
|
|
var d1: CGFloat {
|
|
3.141592653589
|
|
}
|
|
|
|
// MARK: Lifecycle
|
|
|
|
init(a: Int) {
|
|
self.a = a
|
|
}
|
|
|
|
private convenience init() {
|
|
self.init(a: 0)
|
|
}
|
|
|
|
// MARK: Static Functions
|
|
|
|
static func e() {}
|
|
|
|
// MARK: Class Functions
|
|
|
|
class func f() -> Foo {
|
|
Foo()
|
|
}
|
|
|
|
// MARK: Functions
|
|
|
|
func g() -> Int {
|
|
10
|
|
}
|
|
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
options: FormatOptions(categoryMarkComment: "MARK: %c", organizationMode: .type),
|
|
exclude: [.blankLinesAtEndOfScope, .blankLinesAtStartOfScope, .redundantType, .redundantClosure]
|
|
)
|
|
}
|
|
|
|
func testOrganizeEnumCasesFirst() {
|
|
let input = """
|
|
enum Foo {
|
|
init?(rawValue: String) {
|
|
return nil
|
|
}
|
|
|
|
case bar
|
|
case baz
|
|
case quux
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
enum Foo {
|
|
case bar
|
|
case baz
|
|
case quux
|
|
|
|
// MARK: Lifecycle
|
|
|
|
init?(rawValue: String) {
|
|
return nil
|
|
}
|
|
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
exclude: [.blankLinesAtEndOfScope, .unusedArguments]
|
|
)
|
|
}
|
|
|
|
func testPlacingCustomDeclarationsBeforeMarks() {
|
|
let input = """
|
|
public struct Foo {
|
|
|
|
public init() {}
|
|
|
|
public typealias Bar = Int
|
|
|
|
public struct Baz {}
|
|
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
public struct Foo {
|
|
|
|
public typealias Bar = Int
|
|
|
|
public struct Baz {}
|
|
|
|
// MARK: Lifecycle
|
|
|
|
public init() {}
|
|
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
options: FormatOptions(beforeMarks: ["typealias", "struct"]),
|
|
exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope]
|
|
)
|
|
}
|
|
|
|
func testCustomLifecycleMethods() {
|
|
let input = """
|
|
public class ViewController: UIViewController {
|
|
|
|
public init() {
|
|
super.init(nibName: nil, bundle: nil)
|
|
}
|
|
|
|
func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
}
|
|
|
|
func internalInstanceMethod() {}
|
|
|
|
func viewDidAppear(_ animated: Bool) {
|
|
super.viewDidAppear(animated)
|
|
}
|
|
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
public class ViewController: UIViewController {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
public init() {
|
|
super.init(nibName: nil, bundle: nil)
|
|
}
|
|
|
|
func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
}
|
|
|
|
func viewDidAppear(_ animated: Bool) {
|
|
super.viewDidAppear(animated)
|
|
}
|
|
|
|
// MARK: Internal
|
|
|
|
func internalInstanceMethod() {}
|
|
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
options: FormatOptions(lifecycleMethods: ["viewDidLoad", "viewWillAppear", "viewDidAppear"]),
|
|
exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope]
|
|
)
|
|
}
|
|
|
|
func testCustomCategoryMarkTemplate() {
|
|
let input = """
|
|
public struct Foo {
|
|
public init() {}
|
|
public func publicInstanceMethod() {}
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
public struct Foo {
|
|
|
|
// - Lifecycle
|
|
|
|
public init() {}
|
|
|
|
// - Public
|
|
|
|
public func publicInstanceMethod() {}
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
options: FormatOptions(categoryMarkComment: "- %c"),
|
|
exclude: [.blankLinesAtStartOfScope]
|
|
)
|
|
}
|
|
|
|
func testBelowCustomStructOrganizationThreshold() {
|
|
let input = """
|
|
struct StructBelowThreshold {
|
|
init() {}
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input,
|
|
rule: .organizeDeclarations,
|
|
options: FormatOptions(organizeStructThreshold: 2)
|
|
)
|
|
}
|
|
|
|
func testAboveCustomStructOrganizationThreshold() {
|
|
let input = """
|
|
public struct StructAboveThreshold {
|
|
init() {}
|
|
public func instanceMethod() {}
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
public struct StructAboveThreshold {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
init() {}
|
|
|
|
// MARK: Public
|
|
|
|
public func instanceMethod() {}
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
options: FormatOptions(organizeStructThreshold: 2),
|
|
exclude: [.blankLinesAtStartOfScope]
|
|
)
|
|
}
|
|
|
|
func testCustomClassOrganizationThreshold() {
|
|
let input = """
|
|
class ClassBelowThreshold {
|
|
init() {}
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input,
|
|
rule: .organizeDeclarations,
|
|
options: FormatOptions(organizeClassThreshold: 2)
|
|
)
|
|
}
|
|
|
|
func testCustomEnumOrganizationThreshold() {
|
|
let input = """
|
|
enum EnumBelowThreshold {
|
|
case enumCase
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input,
|
|
rule: .organizeDeclarations,
|
|
options: FormatOptions(organizeEnumThreshold: 2)
|
|
)
|
|
}
|
|
|
|
func testBelowCustomExtensionOrganizationThreshold() {
|
|
let input = """
|
|
extension FooBelowThreshold {
|
|
func bar() {}
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input,
|
|
rule: .organizeDeclarations,
|
|
options: FormatOptions(
|
|
organizeTypes: ["class", "struct", "enum", "extension"],
|
|
organizeExtensionThreshold: 2
|
|
)
|
|
)
|
|
}
|
|
|
|
func testAboveCustomExtensionOrganizationThreshold() {
|
|
let input = """
|
|
extension FooBelowThreshold {
|
|
public func bar() {}
|
|
func baz() {}
|
|
private func quux() {}
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
extension FooBelowThreshold {
|
|
|
|
// MARK: Public
|
|
|
|
public func bar() {}
|
|
|
|
// MARK: Internal
|
|
|
|
func baz() {}
|
|
|
|
// MARK: Private
|
|
|
|
private func quux() {}
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
options: FormatOptions(
|
|
organizeTypes: ["class", "struct", "enum", "extension"],
|
|
organizeExtensionThreshold: 2
|
|
), exclude: [.blankLinesAtStartOfScope]
|
|
)
|
|
}
|
|
|
|
func testPreservesExistingMarks() {
|
|
let input = """
|
|
actor Foo {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
init(json: JSONObject) throws {
|
|
bar = try json.value(for: "bar")
|
|
baz = try json.value(for: "baz")
|
|
}
|
|
|
|
// MARK: Internal
|
|
|
|
let bar: String
|
|
let baz: Int?
|
|
}
|
|
"""
|
|
testFormatting(for: input, rule: .organizeDeclarations,
|
|
exclude: [.blankLinesAtStartOfScope])
|
|
}
|
|
|
|
func testUpdatesMalformedMarks() {
|
|
let input = """
|
|
public actor Foo {
|
|
|
|
// MARK: lifecycle
|
|
|
|
// MARK: Lifeycle
|
|
|
|
init() {}
|
|
|
|
// mark: Public
|
|
|
|
// mark - Public
|
|
|
|
public func bar() {}
|
|
|
|
// MARK: - Internal
|
|
|
|
func baz() {}
|
|
|
|
// mrak: privat
|
|
|
|
// Pulse
|
|
|
|
private func quux() {}
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
public actor Foo {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
init() {}
|
|
|
|
// MARK: Public
|
|
|
|
public func bar() {}
|
|
|
|
// MARK: Internal
|
|
|
|
func baz() {}
|
|
|
|
// MARK: Private
|
|
|
|
// Pulse
|
|
|
|
private func quux() {}
|
|
}
|
|
"""
|
|
|
|
testFormatting(for: input, output, rule: .organizeDeclarations,
|
|
exclude: [.blankLinesAtStartOfScope])
|
|
}
|
|
|
|
func testDoesntAttemptToUpdateMarksNotAtTopLevel() {
|
|
let input = """
|
|
public class Foo {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
public init() {
|
|
foo = ["foo"]
|
|
}
|
|
|
|
// Comment at bottom of lifecycle category
|
|
|
|
// MARK: Private
|
|
|
|
@annotation // Private
|
|
/// Private
|
|
private var foo: [String] = []
|
|
|
|
private func bar() {
|
|
// Private
|
|
guard let baz = bar else {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
"""
|
|
|
|
testFormatting(for: input, rule: .organizeDeclarations,
|
|
exclude: [.blankLinesAtStartOfScope, .docCommentsBeforeModifiers])
|
|
}
|
|
|
|
func testHandlesTrailingCommentCorrectly() {
|
|
let input = """
|
|
public class Foo {
|
|
var bar = "bar"
|
|
/// Leading comment
|
|
public var baz = "baz" // Trailing comment
|
|
var quux = "quux"
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
public class Foo {
|
|
|
|
// MARK: Public
|
|
|
|
/// Leading comment
|
|
public var baz = "baz" // Trailing comment
|
|
|
|
// MARK: Internal
|
|
|
|
var bar = "bar"
|
|
var quux = "quux"
|
|
}
|
|
"""
|
|
|
|
testFormatting(for: input, output, rule: .organizeDeclarations,
|
|
exclude: [.blankLinesAtStartOfScope])
|
|
}
|
|
|
|
func testDoesntInsertMarkWhenOnlyOneCategory() {
|
|
let input = """
|
|
class Foo {
|
|
var bar: Int
|
|
var baz: Int
|
|
func instanceMethod() {}
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
class Foo {
|
|
var bar: Int
|
|
var baz: Int
|
|
|
|
func instanceMethod() {}
|
|
}
|
|
"""
|
|
|
|
testFormatting(for: input, output, rule: .organizeDeclarations)
|
|
}
|
|
|
|
func testOrganizesTypesWithinConditionalCompilationBlock() {
|
|
let input = """
|
|
#if DEBUG
|
|
public struct DebugFoo {
|
|
init() {}
|
|
public func instanceMethod() {}
|
|
}
|
|
#else
|
|
public struct ProductionFoo {
|
|
init() {}
|
|
public func instanceMethod() {}
|
|
}
|
|
#endif
|
|
"""
|
|
|
|
let output = """
|
|
#if DEBUG
|
|
public struct DebugFoo {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
init() {}
|
|
|
|
// MARK: Public
|
|
|
|
public func instanceMethod() {}
|
|
}
|
|
#else
|
|
public struct ProductionFoo {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
init() {}
|
|
|
|
// MARK: Public
|
|
|
|
public func instanceMethod() {}
|
|
}
|
|
#endif
|
|
"""
|
|
|
|
testFormatting(for: input, output, rule: .organizeDeclarations,
|
|
options: FormatOptions(ifdefIndent: .noIndent),
|
|
exclude: [.blankLinesAtStartOfScope])
|
|
}
|
|
|
|
func testOrganizesTypesBelowConditionalCompilationBlock() {
|
|
let input = """
|
|
#if canImport(UIKit)
|
|
import UIKit
|
|
#endif
|
|
|
|
public struct Foo {
|
|
init() {}
|
|
public func instanceMethod() {}
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
#if canImport(UIKit)
|
|
import UIKit
|
|
#endif
|
|
|
|
public struct Foo {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
init() {}
|
|
|
|
// MARK: Public
|
|
|
|
public func instanceMethod() {}
|
|
}
|
|
"""
|
|
|
|
testFormatting(for: input, output, rule: .organizeDeclarations,
|
|
options: FormatOptions(ifdefIndent: .noIndent),
|
|
exclude: [.blankLinesAtStartOfScope])
|
|
}
|
|
|
|
func testOrganizesNestedTypesWithinConditionalCompilationBlock() {
|
|
let input = """
|
|
public struct Foo {
|
|
|
|
public var bar = "bar"
|
|
var baz = "baz"
|
|
|
|
#if DEBUG
|
|
public struct DebugFoo {
|
|
init() {}
|
|
var debugBar = "debug"
|
|
}
|
|
|
|
static let debugFoo = DebugFoo()
|
|
|
|
private let other = "other"
|
|
#endif
|
|
|
|
init() {}
|
|
|
|
var quuz = "quux"
|
|
|
|
#if DEBUG
|
|
struct Test {
|
|
let foo: Bar
|
|
}
|
|
#endif
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
public struct Foo {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
init() {}
|
|
|
|
// MARK: Public
|
|
|
|
#if DEBUG
|
|
public struct DebugFoo {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
init() {}
|
|
|
|
// MARK: Internal
|
|
|
|
var debugBar = "debug"
|
|
}
|
|
|
|
static let debugFoo = DebugFoo()
|
|
|
|
private let other = "other"
|
|
#endif
|
|
|
|
public var bar = "bar"
|
|
|
|
// MARK: Internal
|
|
|
|
#if DEBUG
|
|
struct Test {
|
|
let foo: Bar
|
|
}
|
|
#endif
|
|
|
|
var baz = "baz"
|
|
|
|
var quuz = "quux"
|
|
|
|
}
|
|
"""
|
|
|
|
testFormatting(for: input, output, rule: .organizeDeclarations,
|
|
options: FormatOptions(ifdefIndent: .noIndent),
|
|
exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope, .propertyTypes])
|
|
}
|
|
|
|
func testOrganizesTypeBelowSymbolImport() {
|
|
let input = """
|
|
import protocol SomeModule.SomeProtocol
|
|
import class SomeModule.SomeClass
|
|
import enum SomeModule.SomeEnum
|
|
import struct SomeModule.SomeStruct
|
|
import typealias SomeModule.SomeTypealias
|
|
import let SomeModule.SomeGlobalConstant
|
|
import var SomeModule.SomeGlobalVariable
|
|
import func SomeModule.SomeFunc
|
|
|
|
public struct Foo {
|
|
init() {}
|
|
public func instanceMethod() {}
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
import protocol SomeModule.SomeProtocol
|
|
import class SomeModule.SomeClass
|
|
import enum SomeModule.SomeEnum
|
|
import struct SomeModule.SomeStruct
|
|
import typealias SomeModule.SomeTypealias
|
|
import let SomeModule.SomeGlobalConstant
|
|
import var SomeModule.SomeGlobalVariable
|
|
import func SomeModule.SomeFunc
|
|
|
|
public struct Foo {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
init() {}
|
|
|
|
// MARK: Public
|
|
|
|
public func instanceMethod() {}
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output, rule: .organizeDeclarations,
|
|
exclude: [.blankLinesAtStartOfScope, .sortImports]
|
|
)
|
|
}
|
|
|
|
func testDoesntBreakStructSynthesizedMemberwiseInitializer() {
|
|
let input = """
|
|
public struct Foo {
|
|
|
|
let foo: Foo
|
|
@State var bar: Bar?
|
|
@ObservedObject var baaz: Baaz
|
|
public let quux: Quux
|
|
|
|
public var content: some View {
|
|
foo
|
|
}
|
|
}
|
|
|
|
Foo(foo: 1, bar: 2, baaz: 3, quux: 4)
|
|
"""
|
|
|
|
let output = """
|
|
public struct Foo {
|
|
|
|
// MARK: Public
|
|
|
|
public var content: some View {
|
|
foo
|
|
}
|
|
|
|
// MARK: Internal
|
|
|
|
let foo: Foo
|
|
|
|
@State var bar: Bar?
|
|
@ObservedObject var baaz: Baaz
|
|
|
|
public let quux: Quux
|
|
|
|
}
|
|
|
|
Foo(foo: 1, bar: 2, baaz: 3, quux: 4)
|
|
"""
|
|
|
|
testFormatting(for: input, [output], rules: [.organizeDeclarations, .consecutiveBlankLines], exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope, .privateStateVariables])
|
|
}
|
|
|
|
func testOrganizesStructPropertiesThatDontBreakMemberwiseInitializer() {
|
|
let input = """
|
|
public struct Foo {
|
|
var computed: String {
|
|
let didSet = "didSet"
|
|
let willSet = "willSet"
|
|
return didSet + willSet
|
|
}
|
|
|
|
private func instanceMethod() {}
|
|
public let bar: Int
|
|
var baz: Int
|
|
var quux: Int {
|
|
didSet {}
|
|
}
|
|
}
|
|
|
|
Foo(bar: 1, baz: 2, quux: 3)
|
|
"""
|
|
|
|
let output = """
|
|
public struct Foo {
|
|
|
|
// MARK: Public
|
|
|
|
public let bar: Int
|
|
|
|
// MARK: Internal
|
|
|
|
var baz: Int
|
|
|
|
var computed: String {
|
|
let didSet = "didSet"
|
|
let willSet = "willSet"
|
|
return didSet + willSet
|
|
}
|
|
|
|
var quux: Int {
|
|
didSet {}
|
|
}
|
|
|
|
// MARK: Private
|
|
|
|
private func instanceMethod() {}
|
|
}
|
|
|
|
Foo(bar: 1, baz: 2, quux: 3)
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output, rule: .organizeDeclarations,
|
|
exclude: [.blankLinesAtStartOfScope]
|
|
)
|
|
}
|
|
|
|
func testPreservesCategoryMarksInStructWithIncorrectSubcategoryOrdering() {
|
|
let input = """
|
|
public struct Foo {
|
|
|
|
// MARK: Public
|
|
|
|
public let quux: Int
|
|
|
|
// MARK: Internal
|
|
|
|
var bar: Int {
|
|
didSet {}
|
|
}
|
|
|
|
var baz: Int
|
|
}
|
|
|
|
Foo(bar: 1, baz: 2, quux: 3)
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, rule: .organizeDeclarations,
|
|
exclude: [.blankLinesAtStartOfScope]
|
|
)
|
|
}
|
|
|
|
func testPreservesCommentsAtBottomOfCategory() {
|
|
let input = """
|
|
public struct Foo {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
init() {}
|
|
|
|
// Important comment at end of section!
|
|
|
|
// MARK: Public
|
|
|
|
public let bar = 1
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, rule: .organizeDeclarations,
|
|
exclude: [.blankLinesAtStartOfScope]
|
|
)
|
|
}
|
|
|
|
func testPreservesCommentsAtBottomOfCategoryWhenReorganizing() {
|
|
let input = """
|
|
public struct Foo {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
init() {}
|
|
|
|
// Important comment at end of section!
|
|
|
|
// MARK: Internal
|
|
|
|
// Important comment at start of section!
|
|
|
|
var baz = 1
|
|
|
|
public let bar = 1
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
public struct Foo {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
init() {}
|
|
|
|
// Important comment at end of section!
|
|
|
|
// MARK: Public
|
|
|
|
public let bar = 1
|
|
|
|
// MARK: Internal
|
|
|
|
// Important comment at start of section!
|
|
|
|
var baz = 1
|
|
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output, rule: .organizeDeclarations,
|
|
exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope]
|
|
)
|
|
}
|
|
|
|
func testDoesntRemoveCategorySeparatorsFromBodyNotBeingOrganized() {
|
|
let input = """
|
|
public struct Foo {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
init() {}
|
|
|
|
// MARK: Public
|
|
|
|
public var bar = 10
|
|
}
|
|
|
|
extension Foo {
|
|
|
|
// MARK: Public
|
|
|
|
public var baz: Int { 20 }
|
|
|
|
// MARK: Internal
|
|
|
|
var quux: Int { 30 }
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, rule: .organizeDeclarations,
|
|
options: FormatOptions(organizeStructThreshold: 20),
|
|
exclude: [.blankLinesAtStartOfScope, .wrapPropertyBodies]
|
|
)
|
|
}
|
|
|
|
func testParsesPropertiesWithBodies() {
|
|
let input = """
|
|
class Foo {
|
|
// Instance properties without bodies:
|
|
|
|
let propertyWithoutBody1 = 10
|
|
|
|
let propertyWithoutBody2: String = {
|
|
"bar"
|
|
}()
|
|
|
|
let propertyWithoutBody3: () -> String = {
|
|
"bar"
|
|
}
|
|
|
|
// Instance properties with bodies:
|
|
|
|
var withBody1: String {
|
|
"bar"
|
|
}
|
|
|
|
var withBody2: String {
|
|
didSet { print("didSet") }
|
|
}
|
|
|
|
var withBody3: String = "bar" {
|
|
didSet { print("didSet") }
|
|
}
|
|
|
|
var withBody4: String = "bar" {
|
|
didSet { print("didSet") }
|
|
}
|
|
|
|
var withBody5: () -> String = { "bar" } {
|
|
didSet { print("didSet") }
|
|
}
|
|
|
|
var withBody6: String = { "bar" }() {
|
|
didSet { print("didSet") }
|
|
}
|
|
}
|
|
"""
|
|
|
|
testFormatting(for: input, rule: .organizeDeclarations, exclude: [.redundantClosure, .wrapPropertyBodies])
|
|
}
|
|
|
|
func testFuncWithNestedInitNotTreatedAsLifecycle() {
|
|
let input = """
|
|
public struct Foo {
|
|
|
|
// MARK: Public
|
|
|
|
public func baz() {}
|
|
|
|
// MARK: Internal
|
|
|
|
func bar() {
|
|
class NestedClass {
|
|
init() {}
|
|
}
|
|
|
|
// ...
|
|
}
|
|
}
|
|
"""
|
|
|
|
testFormatting(for: input, rule: .organizeDeclarations,
|
|
exclude: [.blankLinesAtStartOfScope])
|
|
}
|
|
|
|
func testOrganizeRuleNotConfusedByClassProtocol() {
|
|
let input = """
|
|
protocol Foo: class {
|
|
func foo()
|
|
}
|
|
|
|
class Bar {
|
|
// MARK: Fileprivate
|
|
|
|
private var baz: Int
|
|
|
|
// MARK: Private
|
|
|
|
private let quux: String
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
protocol Foo: class {
|
|
func foo()
|
|
}
|
|
|
|
class Bar {
|
|
private var baz: Int
|
|
|
|
private let quux: String
|
|
}
|
|
"""
|
|
|
|
testFormatting(for: input, output, rule: .organizeDeclarations,
|
|
exclude: [.blankLinesAtStartOfScope, .blankLinesAroundMark])
|
|
}
|
|
|
|
func testOrganizeClassDeclarationsIntoCategoriesWithNoBlankLineAfterMark() {
|
|
let input = """
|
|
public class Foo {
|
|
private func privateMethod() {}
|
|
|
|
private let bar = 1
|
|
public let baz = 1
|
|
open var quack = 2
|
|
var quux = 2
|
|
|
|
init() {}
|
|
|
|
/// Doc comment
|
|
public func publicMethod() {}
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
public class Foo {
|
|
|
|
// MARK: Lifecycle
|
|
init() {}
|
|
|
|
// MARK: Open
|
|
open var quack = 2
|
|
|
|
// MARK: Public
|
|
public let baz = 1
|
|
|
|
/// Doc comment
|
|
public func publicMethod() {}
|
|
|
|
// MARK: Internal
|
|
var quux = 2
|
|
|
|
// MARK: Private
|
|
private let bar = 1
|
|
|
|
private func privateMethod() {}
|
|
|
|
}
|
|
"""
|
|
let options = FormatOptions(lineAfterMarks: false)
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
options: options,
|
|
exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope]
|
|
)
|
|
}
|
|
|
|
func testOrganizeWithNoCategoryMarks_noSpacesBetweenDeclarations() {
|
|
let input = """
|
|
public class Foo {
|
|
private func privateMethod() {}
|
|
private let bar = 1
|
|
public let baz = 1
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
public class Foo {
|
|
public let baz = 1
|
|
|
|
private let bar = 1
|
|
|
|
private func privateMethod() {}
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
options: FormatOptions(markCategories: false)
|
|
)
|
|
}
|
|
|
|
func testOrganizeWithNoCategoryMarks_withSpacesBetweenDeclarations() {
|
|
let input = """
|
|
public class Foo {
|
|
private func privateMethod() {}
|
|
|
|
private let bar = 1
|
|
|
|
public let baz = 1
|
|
|
|
private func anotherPrivateMethod() {}
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
public class Foo {
|
|
public let baz = 1
|
|
|
|
private let bar = 1
|
|
|
|
private func privateMethod() {}
|
|
|
|
private func anotherPrivateMethod() {}
|
|
}
|
|
"""
|
|
|
|
// easy to start with?
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
options: FormatOptions(markCategories: false)
|
|
)
|
|
}
|
|
|
|
func testOrganizeConditionalInitDeclaration() {
|
|
let input = """
|
|
class Foo {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
init() {}
|
|
|
|
#if DEBUG
|
|
init() {
|
|
print("Debug")
|
|
}
|
|
#endif
|
|
|
|
// MARK: Internal
|
|
|
|
func test() {}
|
|
}
|
|
"""
|
|
|
|
testFormatting(for: input, rule: .organizeDeclarations, options: FormatOptions(ifdefIndent: .noIndent), exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope])
|
|
}
|
|
|
|
func testOrganizeConditionalPublicFunction() {
|
|
let input = """
|
|
public class Foo {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
init() {}
|
|
|
|
// MARK: Public
|
|
|
|
#if DEBUG
|
|
public func publicTest() {}
|
|
#endif
|
|
|
|
// MARK: Internal
|
|
|
|
func internalTest() {}
|
|
}
|
|
"""
|
|
|
|
testFormatting(for: input, rule: .organizeDeclarations, options: FormatOptions(ifdefIndent: .noIndent), exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope])
|
|
}
|
|
|
|
func testDoesntConflictWithOrganizeDeclarations() {
|
|
let input = """
|
|
// swiftformat:sort
|
|
enum FeatureFlags {
|
|
case barFeature
|
|
case fooFeature
|
|
case upsellA
|
|
case upsellB
|
|
|
|
// MARK: Internal
|
|
|
|
var anUnsortedProperty: Foo {
|
|
Foo()
|
|
}
|
|
|
|
var unsortedProperty: Foo {
|
|
Foo()
|
|
}
|
|
}
|
|
"""
|
|
|
|
testFormatting(for: input, rule: .organizeDeclarations)
|
|
}
|
|
|
|
func testSortsWithinOrganizeDeclarations() {
|
|
let input = """
|
|
// swiftformat:sort
|
|
enum FeatureFlags {
|
|
case fooFeature
|
|
case barFeature
|
|
case upsellB
|
|
case upsellA
|
|
|
|
// MARK: Internal
|
|
|
|
var sortedProperty: Foo {
|
|
Foo()
|
|
}
|
|
|
|
var aSortedProperty: Foo {
|
|
Foo()
|
|
}
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
// swiftformat:sort
|
|
enum FeatureFlags {
|
|
case barFeature
|
|
case fooFeature
|
|
case upsellA
|
|
|
|
case upsellB
|
|
|
|
// MARK: Internal
|
|
|
|
var aSortedProperty: Foo {
|
|
Foo()
|
|
}
|
|
|
|
var sortedProperty: Foo {
|
|
Foo()
|
|
}
|
|
|
|
}
|
|
"""
|
|
|
|
testFormatting(for: input, [output],
|
|
rules: [.organizeDeclarations, .blankLinesBetweenScopes],
|
|
exclude: [.blankLinesAtEndOfScope])
|
|
}
|
|
|
|
func testSortsWithinOrganizeDeclarationsByClassName() {
|
|
let input = """
|
|
enum FeatureFlags {
|
|
case fooFeature
|
|
case barFeature
|
|
case upsellB
|
|
case upsellA
|
|
|
|
// MARK: Internal
|
|
|
|
var sortedProperty: Foo {
|
|
Foo()
|
|
}
|
|
|
|
var aSortedProperty: Foo {
|
|
Foo()
|
|
}
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
enum FeatureFlags {
|
|
case barFeature
|
|
case fooFeature
|
|
case upsellA
|
|
|
|
case upsellB
|
|
|
|
// MARK: Internal
|
|
|
|
var aSortedProperty: Foo {
|
|
Foo()
|
|
}
|
|
|
|
var sortedProperty: Foo {
|
|
Foo()
|
|
}
|
|
|
|
}
|
|
"""
|
|
|
|
testFormatting(for: input, [output],
|
|
rules: [.organizeDeclarations, .blankLinesBetweenScopes],
|
|
options: .init(alphabeticallySortedDeclarationPatterns: ["FeatureFlags"]),
|
|
exclude: [.blankLinesAtEndOfScope])
|
|
}
|
|
|
|
func testSortsWithinOrganizeDeclarationsByPartialClassName() {
|
|
let input = """
|
|
enum FeatureFlags {
|
|
case fooFeature
|
|
case barFeature
|
|
case upsellB
|
|
case upsellA
|
|
|
|
// MARK: Internal
|
|
|
|
var sortedProperty: Foo {
|
|
Foo()
|
|
}
|
|
|
|
var aSortedProperty: Foo {
|
|
Foo()
|
|
}
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
enum FeatureFlags {
|
|
case barFeature
|
|
case fooFeature
|
|
case upsellA
|
|
|
|
case upsellB
|
|
|
|
// MARK: Internal
|
|
|
|
var aSortedProperty: Foo {
|
|
Foo()
|
|
}
|
|
|
|
var sortedProperty: Foo {
|
|
Foo()
|
|
}
|
|
|
|
}
|
|
"""
|
|
|
|
testFormatting(for: input, [output],
|
|
rules: [.organizeDeclarations, .blankLinesBetweenScopes],
|
|
options: .init(alphabeticallySortedDeclarationPatterns: ["ureFla"]),
|
|
exclude: [.blankLinesAtEndOfScope])
|
|
}
|
|
|
|
func testDontSortsWithinOrganizeDeclarationsByClassNameInComment() {
|
|
let input = """
|
|
/// Comment
|
|
enum FeatureFlags {
|
|
case fooFeature
|
|
case barFeature
|
|
case upsellB
|
|
case upsellA
|
|
|
|
// MARK: Internal
|
|
|
|
var sortedProperty: Foo {
|
|
Foo()
|
|
}
|
|
|
|
var aSortedProperty: Foo {
|
|
Foo()
|
|
}
|
|
}
|
|
"""
|
|
|
|
testFormatting(for: input,
|
|
rules: [.organizeDeclarations, .blankLinesBetweenScopes],
|
|
options: .init(alphabeticallySortedDeclarationPatterns: ["Comment"]),
|
|
exclude: [.blankLinesAtEndOfScope])
|
|
}
|
|
|
|
func testOrganizeDeclarationsSortUsesLocalizedCompare() {
|
|
let input = """
|
|
// swiftformat:sort
|
|
enum FeatureFlags {
|
|
case upsella
|
|
case upsellA
|
|
case upsellb
|
|
case upsellB
|
|
}
|
|
"""
|
|
|
|
testFormatting(for: input, rule: .organizeDeclarations)
|
|
}
|
|
|
|
func testSortDeclarationsSortsExtensionBody() {
|
|
let input = """
|
|
public enum Namespace {}
|
|
|
|
// swiftformat:sort
|
|
extension Namespace {
|
|
static let foo = "foo"
|
|
public static let bar = "bar"
|
|
static let baaz = "baaz"
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
public enum Namespace {}
|
|
|
|
// swiftformat:sort
|
|
extension Namespace {
|
|
static let baaz = "baaz"
|
|
public static let bar = "bar"
|
|
static let foo = "foo"
|
|
}
|
|
"""
|
|
|
|
// organizeTypes doesn't include "extension". So even though the
|
|
// organizeDeclarations rule is enabled, the extension should be
|
|
// sorted by the sortDeclarations rule.
|
|
let options = FormatOptions(organizeTypes: ["class"])
|
|
testFormatting(for: input, [output], rules: [.sortDeclarations, .organizeDeclarations], options: options)
|
|
}
|
|
|
|
func testOrganizeDeclarationsSortsExtensionBody() {
|
|
let input = """
|
|
public enum Namespace {}
|
|
|
|
// swiftformat:sort
|
|
extension Namespace {
|
|
static let foo = "foo"
|
|
public static let bar = "bar"
|
|
static let baaz = "baaz"
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
public enum Namespace {}
|
|
|
|
// swiftformat:sort
|
|
extension Namespace {
|
|
|
|
// MARK: Public
|
|
|
|
public static let bar = "bar"
|
|
|
|
// MARK: Internal
|
|
|
|
static let baaz = "baaz"
|
|
static let foo = "foo"
|
|
}
|
|
"""
|
|
|
|
let options = FormatOptions(organizeTypes: ["extension"])
|
|
testFormatting(for: input, output, rule: .organizeDeclarations, options: options,
|
|
exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope])
|
|
}
|
|
|
|
func testOrganizeDeclarationsContainingNonisolated() {
|
|
let input = """
|
|
public class Test {
|
|
public static func test1() {}
|
|
|
|
private nonisolated(unsafe) static var test3: ((
|
|
_ arg1: Bool,
|
|
_ arg2: Int
|
|
) -> Bool)?
|
|
|
|
static func test2() {}
|
|
}
|
|
"""
|
|
let output = """
|
|
public class Test {
|
|
|
|
// MARK: Public
|
|
|
|
public static func test1() {}
|
|
|
|
// MARK: Internal
|
|
|
|
static func test2() {}
|
|
|
|
// MARK: Private
|
|
|
|
private nonisolated(unsafe) static var test3: ((
|
|
_ arg1: Bool,
|
|
_ arg2: Int
|
|
) -> Bool)?
|
|
|
|
}
|
|
"""
|
|
testFormatting(for: input, output, rule: .organizeDeclarations,
|
|
exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope])
|
|
}
|
|
|
|
func testSortStructPropertiesWithAttributes() {
|
|
let input = """
|
|
// swiftformat:sort
|
|
struct BookReaderView {
|
|
@Namespace private var animation
|
|
@State private var animationContent: Bool = false
|
|
@State private var offsetY: CGFloat = 0
|
|
@Bindable var model: Book
|
|
@Query(
|
|
filter: #Predicate<TextContent> { $0.progress_ < 1 },
|
|
sort: \\.updatedAt_,
|
|
order: .reverse
|
|
) private var incompleteTextContents: [TextContent]
|
|
}
|
|
"""
|
|
let output = """
|
|
// swiftformat:sort
|
|
struct BookReaderView {
|
|
|
|
// MARK: Internal
|
|
|
|
@Bindable var model: Book
|
|
|
|
// MARK: Private
|
|
|
|
@Namespace private var animation
|
|
@State private var animationContent: Bool = false
|
|
@Query(
|
|
filter: #Predicate<TextContent> { $0.progress_ < 1 },
|
|
sort: \\.updatedAt_,
|
|
order: .reverse
|
|
) private var incompleteTextContents: [TextContent]
|
|
@State private var offsetY: CGFloat = 0
|
|
}
|
|
"""
|
|
let options = FormatOptions(indent: " ", organizeTypes: ["struct"])
|
|
testFormatting(for: input, output, rule: .organizeDeclarations,
|
|
options: options, exclude: [.blankLinesAtStartOfScope])
|
|
}
|
|
|
|
func testSortSingleSwiftUIPropertyWrapper() {
|
|
let input = """
|
|
struct ContentView: View {
|
|
|
|
init(label: String) {
|
|
self.label = label
|
|
}
|
|
|
|
private var label: String
|
|
|
|
@State
|
|
private var isOn: Bool = false
|
|
|
|
private var foo = true
|
|
|
|
@ViewBuilder
|
|
private var toggle: some View {
|
|
Toggle(label, isOn: $isOn)
|
|
}
|
|
|
|
@ViewBuilder
|
|
var body: some View {
|
|
toggle
|
|
}
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
struct ContentView: View {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
init(label: String) {
|
|
self.label = label
|
|
}
|
|
|
|
// MARK: Internal
|
|
|
|
@ViewBuilder
|
|
var body: some View {
|
|
toggle
|
|
}
|
|
|
|
// MARK: Private
|
|
|
|
@State
|
|
private var isOn: Bool = false
|
|
|
|
private var label: String
|
|
|
|
private var foo = true
|
|
|
|
@ViewBuilder
|
|
private var toggle: some View {
|
|
Toggle(label, isOn: $isOn)
|
|
}
|
|
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
options: FormatOptions(organizeTypes: ["struct"], organizationMode: .visibility),
|
|
exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope, .privateStateVariables, .redundantViewBuilder]
|
|
)
|
|
}
|
|
|
|
func testSortMultipleSwiftUIPropertyWrappers() {
|
|
let input = """
|
|
struct ContentView: View {
|
|
|
|
init(foo: Foo, baaz: Baaz) {
|
|
self.foo = foo
|
|
self.baaz = baaz
|
|
}
|
|
|
|
let foo: Foo
|
|
@State var bar = true
|
|
let baaz: Baaz
|
|
@State var quux = true
|
|
|
|
@ViewBuilder
|
|
private var toggle: some View {
|
|
Toggle(label, isOn: $isOn)
|
|
}
|
|
|
|
@ViewBuilder
|
|
var body: some View {
|
|
toggle
|
|
}
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
struct ContentView: View {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
init(foo: Foo, baaz: Baaz) {
|
|
self.foo = foo
|
|
self.baaz = baaz
|
|
}
|
|
|
|
// MARK: Internal
|
|
|
|
@State var bar = true
|
|
@State var quux = true
|
|
|
|
let foo: Foo
|
|
let baaz: Baaz
|
|
|
|
@ViewBuilder
|
|
var body: some View {
|
|
toggle
|
|
}
|
|
|
|
// MARK: Private
|
|
|
|
@ViewBuilder
|
|
private var toggle: some View {
|
|
Toggle(label, isOn: $isOn)
|
|
}
|
|
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
options: FormatOptions(organizeTypes: ["struct"], organizationMode: .visibility),
|
|
exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope, .privateStateVariables, .redundantMemberwiseInit, .redundantViewBuilder]
|
|
)
|
|
}
|
|
|
|
func testSortSwiftUIPropertyWrappersWithDifferentVisibility() {
|
|
let input = """
|
|
struct ContentView: View {
|
|
|
|
init(foo: Foo, baaz: Baaz, isOn: Binding<Bool>) {
|
|
self.foo = foo
|
|
self.baaz = baaz
|
|
self_.isOn = isOn
|
|
}
|
|
|
|
let foo: Foo
|
|
@State private var bar = 0
|
|
private let baaz: Baaz
|
|
@Binding var isOn: Bool
|
|
|
|
@ViewBuilder
|
|
private var toggle: some View {
|
|
Toggle(label, isOn: $isOn)
|
|
}
|
|
|
|
@ViewBuilder
|
|
var body: some View {
|
|
toggle
|
|
}
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
struct ContentView: View {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
init(foo: Foo, baaz: Baaz, isOn: Binding<Bool>) {
|
|
self.foo = foo
|
|
self.baaz = baaz
|
|
self_.isOn = isOn
|
|
}
|
|
|
|
// MARK: Internal
|
|
|
|
@Binding var isOn: Bool
|
|
|
|
let foo: Foo
|
|
|
|
@ViewBuilder
|
|
var body: some View {
|
|
toggle
|
|
}
|
|
|
|
// MARK: Private
|
|
|
|
@State private var bar = 0
|
|
|
|
private let baaz: Baaz
|
|
|
|
@ViewBuilder
|
|
private var toggle: some View {
|
|
Toggle(label, isOn: $isOn)
|
|
}
|
|
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
options: FormatOptions(organizeTypes: ["struct"], organizationMode: .visibility),
|
|
exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope, .redundantViewBuilder]
|
|
)
|
|
}
|
|
|
|
func testSortSwiftUIPropertyWrappersWithArguments() {
|
|
let input = """
|
|
struct ContentView: View {
|
|
|
|
init(foo: Foo, baaz: Baaz) {
|
|
self.foo = foo
|
|
self.baaz = baaz
|
|
}
|
|
|
|
let foo: Foo
|
|
@Environment(\\.colorScheme) var colorScheme
|
|
let baaz: Baaz
|
|
@Environment(\\.quux) let quux: Quux
|
|
|
|
@ViewBuilder
|
|
private var toggle: some View {
|
|
Toggle(label, isOn: $isOn)
|
|
}
|
|
|
|
@ViewBuilder
|
|
var body: some View {
|
|
toggle
|
|
}
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
struct ContentView: View {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
init(foo: Foo, baaz: Baaz) {
|
|
self.foo = foo
|
|
self.baaz = baaz
|
|
}
|
|
|
|
// MARK: Internal
|
|
|
|
@Environment(\\.colorScheme) var colorScheme
|
|
@Environment(\\.quux) let quux: Quux
|
|
|
|
let foo: Foo
|
|
let baaz: Baaz
|
|
|
|
@ViewBuilder
|
|
var body: some View {
|
|
toggle
|
|
}
|
|
|
|
// MARK: Private
|
|
|
|
@ViewBuilder
|
|
private var toggle: some View {
|
|
Toggle(label, isOn: $isOn)
|
|
}
|
|
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
options: FormatOptions(organizeTypes: ["struct"], organizationMode: .visibility),
|
|
exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope, .redundantViewBuilder]
|
|
)
|
|
}
|
|
|
|
func testDoesntAddUnexpectedBlankLinesDueToBlankLinesWithSpaces() {
|
|
// The blank lines in this input code are indented with four spaces.
|
|
// Done using string interpolation in the input code to make this
|
|
// more clear, and to prevent the spaces from being removed automatically.
|
|
let input = """
|
|
public class TestClass {
|
|
var variable01 = 1
|
|
var variable02 = 2
|
|
var variable03 = 3
|
|
var variable04 = 4
|
|
var variable05 = 5
|
|
\(" ")
|
|
public func foo() {}
|
|
\(" ")
|
|
func bar() {}
|
|
\(" ")
|
|
private func baz() {}
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
public class TestClass {
|
|
|
|
// MARK: Public
|
|
|
|
public func foo() {}
|
|
\(" ")
|
|
// MARK: Internal
|
|
|
|
var variable01 = 1
|
|
var variable02 = 2
|
|
var variable03 = 3
|
|
var variable04 = 4
|
|
var variable05 = 5
|
|
\(" ")
|
|
func bar() {}
|
|
\(" ")
|
|
// MARK: Private
|
|
|
|
private func baz() {}
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope, .consecutiveBlankLines, .trailingSpace, .consecutiveSpaces, .indent]
|
|
)
|
|
}
|
|
|
|
func testSortSwiftUIPropertyWrappersSubCategoryAlphabetically() {
|
|
let input = """
|
|
struct ContentView: View {
|
|
init() {}
|
|
|
|
@Environment(\\.colorScheme) var colorScheme
|
|
@State var foo: Foo
|
|
@Binding var isOn: Bool
|
|
@Environment(\\.quux) var quux: Quux
|
|
@Bindable var model: MyModel
|
|
|
|
@ViewBuilder
|
|
var body: some View {
|
|
Toggle(label, isOn: $isOn)
|
|
}
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
struct ContentView: View {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
init() {}
|
|
|
|
// MARK: Internal
|
|
|
|
@Bindable var model: MyModel
|
|
@Binding var isOn: Bool
|
|
@Environment(\\.colorScheme) var colorScheme
|
|
@Environment(\\.quux) var quux: Quux
|
|
@State var foo: Foo
|
|
|
|
@ViewBuilder
|
|
var body: some View {
|
|
Toggle(label, isOn: $isOn)
|
|
}
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
options: FormatOptions(
|
|
organizeTypes: ["struct"],
|
|
organizationMode: .visibility,
|
|
blankLineAfterSubgroups: false,
|
|
swiftUIPropertiesSortMode: .alphabetize
|
|
),
|
|
exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope, .privateStateVariables, .redundantViewBuilder]
|
|
)
|
|
}
|
|
|
|
func testSortSwiftUIWrappersByTypeAndMaintainGroupSpacingAlphabetically() {
|
|
let input = """
|
|
struct ContentView: View {
|
|
init() {}
|
|
|
|
@State var foo: Foo
|
|
@State var bar: Bar
|
|
|
|
@Environment(\\.colorScheme) var colorScheme
|
|
@Environment(\\.quux) var quux: Quux
|
|
|
|
@Binding var isOn: Bool
|
|
|
|
@ViewBuilder
|
|
var body: some View {
|
|
Toggle(label, isOn: $isOn)
|
|
}
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
struct ContentView: View {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
init() {}
|
|
|
|
// MARK: Internal
|
|
|
|
@Binding var isOn: Bool
|
|
|
|
@Environment(\\.colorScheme) var colorScheme
|
|
@Environment(\\.quux) var quux: Quux
|
|
|
|
@State var foo: Foo
|
|
@State var bar: Bar
|
|
|
|
@ViewBuilder
|
|
var body: some View {
|
|
Toggle(label, isOn: $isOn)
|
|
}
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
options: FormatOptions(
|
|
organizeTypes: ["struct"],
|
|
organizationMode: .visibility,
|
|
blankLineAfterSubgroups: false,
|
|
swiftUIPropertiesSortMode: .alphabetize
|
|
),
|
|
exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope, .privateStateVariables, .redundantViewBuilder]
|
|
)
|
|
}
|
|
|
|
func testSortSwiftUIPropertyWrappersSubCategoryPreservingGroupPosition() {
|
|
let input = """
|
|
struct ContentView: View {
|
|
init() {}
|
|
|
|
@Environment(\\.colorScheme) var colorScheme
|
|
@State var foo: Foo
|
|
@Binding var isOn: Bool
|
|
@Environment(\\.quux) var quux: Quux
|
|
|
|
@ViewBuilder
|
|
var body: some View {
|
|
Toggle(label, isOn: $isOn)
|
|
}
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
struct ContentView: View {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
init() {}
|
|
|
|
// MARK: Internal
|
|
|
|
@Environment(\\.colorScheme) var colorScheme
|
|
@Environment(\\.quux) var quux: Quux
|
|
@State var foo: Foo
|
|
@Binding var isOn: Bool
|
|
|
|
@ViewBuilder
|
|
var body: some View {
|
|
Toggle(label, isOn: $isOn)
|
|
}
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
options: FormatOptions(
|
|
organizeTypes: ["struct"],
|
|
organizationMode: .visibility,
|
|
blankLineAfterSubgroups: false,
|
|
swiftUIPropertiesSortMode: .firstAppearanceSort
|
|
),
|
|
exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope, .privateStateVariables, .redundantViewBuilder]
|
|
)
|
|
}
|
|
|
|
func testSortSwiftUIWrappersByTypeAndMaintainGroupSpacingAndPosition() {
|
|
let input = """
|
|
struct ContentView: View {
|
|
init() {}
|
|
|
|
@State var foo: Foo
|
|
@State var bar: Bar
|
|
|
|
@Environment(\\.colorScheme) var colorScheme
|
|
@Environment(\\.quux) var quux: Quux
|
|
|
|
@Binding var isOn: Bool
|
|
|
|
@ViewBuilder
|
|
var body: some View {
|
|
Toggle(label, isOn: $isOn)
|
|
}
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
struct ContentView: View {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
init() {}
|
|
|
|
// MARK: Internal
|
|
|
|
@State var foo: Foo
|
|
@State var bar: Bar
|
|
|
|
@Environment(\\.colorScheme) var colorScheme
|
|
@Environment(\\.quux) var quux: Quux
|
|
|
|
@Binding var isOn: Bool
|
|
|
|
@ViewBuilder
|
|
var body: some View {
|
|
Toggle(label, isOn: $isOn)
|
|
}
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
options: FormatOptions(
|
|
organizeTypes: ["struct"],
|
|
organizationMode: .visibility,
|
|
blankLineAfterSubgroups: false,
|
|
swiftUIPropertiesSortMode: .firstAppearanceSort
|
|
),
|
|
exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope, .privateStateVariables, .redundantViewBuilder]
|
|
)
|
|
}
|
|
|
|
func testPreservesBlockOfConsecutivePropertiesWithoutBlankLinesBetweenSubgroups1() {
|
|
let input = """
|
|
class Foo {
|
|
init() {}
|
|
|
|
let foo: Foo
|
|
let baaz: Baaz
|
|
static let bar: Bar
|
|
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
class Foo {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
init() {}
|
|
|
|
// MARK: Internal
|
|
|
|
static let bar: Bar
|
|
let foo: Foo
|
|
let baaz: Baaz
|
|
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
options: FormatOptions(blankLineAfterSubgroups: false),
|
|
exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope]
|
|
)
|
|
}
|
|
|
|
func testPreservesBlockOfConsecutivePropertiesWithoutBlankLinesBetweenSubgroups2() {
|
|
let input = """
|
|
class Foo {
|
|
init() {}
|
|
|
|
let foo: Foo
|
|
let baaz: Baaz
|
|
static let bar: Bar
|
|
|
|
static let quux: Quux
|
|
let fooBar: FooBar
|
|
let baazQuux: BaazQuux
|
|
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
class Foo {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
init() {}
|
|
|
|
// MARK: Internal
|
|
|
|
static let bar: Bar
|
|
static let quux: Quux
|
|
let foo: Foo
|
|
let baaz: Baaz
|
|
|
|
let fooBar: FooBar
|
|
let baazQuux: BaazQuux
|
|
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
options: FormatOptions(blankLineAfterSubgroups: false),
|
|
exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope]
|
|
)
|
|
}
|
|
|
|
func testPreservesBlockOfConsecutiveProperties() {
|
|
let input = """
|
|
class Foo {
|
|
init() {}
|
|
|
|
let foo: Foo
|
|
let baaz: Baaz
|
|
static let bar: Bar
|
|
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
class Foo {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
init() {}
|
|
|
|
// MARK: Internal
|
|
|
|
static let bar: Bar
|
|
|
|
let foo: Foo
|
|
let baaz: Baaz
|
|
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope]
|
|
)
|
|
}
|
|
|
|
func testPreservesBlockOfConsecutiveProperties2() {
|
|
let input = """
|
|
class Foo {
|
|
init() {}
|
|
|
|
let foo: Foo
|
|
let baaz: Baaz
|
|
static let bar: Bar
|
|
|
|
static let quux: Quux
|
|
let fooBar: FooBar
|
|
let baazQuux: BaazQuux
|
|
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
class Foo {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
init() {}
|
|
|
|
// MARK: Internal
|
|
|
|
static let bar: Bar
|
|
static let quux: Quux
|
|
|
|
let foo: Foo
|
|
let baaz: Baaz
|
|
|
|
let fooBar: FooBar
|
|
let baazQuux: BaazQuux
|
|
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope]
|
|
)
|
|
}
|
|
|
|
func testPreservesCommentAtEndOfTypeBody() {
|
|
let input = """
|
|
class Foo {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
init() {}
|
|
|
|
// MARK: Internal
|
|
|
|
let bar: Bar
|
|
let baaz: Baaz
|
|
|
|
// Comment at end of file
|
|
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input,
|
|
rule: .organizeDeclarations,
|
|
exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope]
|
|
)
|
|
}
|
|
|
|
func testSwiftUIPropertyWrappersSortDoesntBreakViewSynthesizedMemberwiseInitializer() {
|
|
// @Environment properties don't affect memberwise init, so they can be freely reordered.
|
|
// The stored properties (foo, baaz) maintain their relative order to preserve memberwise init.
|
|
let input = """
|
|
struct ContentView: View {
|
|
|
|
let foo: Foo
|
|
@Environment(\\.colorScheme) private var colorScheme
|
|
let baaz: Baaz
|
|
@Environment(\\.quux) private let quux: Quux
|
|
|
|
@ViewBuilder
|
|
private var toggle: some View {
|
|
Toggle(label, isOn: $isOn)
|
|
}
|
|
|
|
@ViewBuilder
|
|
var body: some View {
|
|
toggle
|
|
}
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
struct ContentView: View {
|
|
|
|
// MARK: Internal
|
|
|
|
let foo: Foo
|
|
let baaz: Baaz
|
|
|
|
@ViewBuilder
|
|
var body: some View {
|
|
toggle
|
|
}
|
|
|
|
// MARK: Private
|
|
|
|
@Environment(\\.colorScheme) private var colorScheme
|
|
@Environment(\\.quux) private let quux: Quux
|
|
|
|
@ViewBuilder
|
|
private var toggle: some View {
|
|
Toggle(label, isOn: $isOn)
|
|
}
|
|
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
options: FormatOptions(organizeTypes: ["struct"], organizationMode: .visibility),
|
|
exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope, .redundantViewBuilder]
|
|
)
|
|
}
|
|
|
|
func testReorderingPropertiesCreatesFormatterChanges() {
|
|
let input = """
|
|
struct Test {
|
|
var bar: Bar { "Bar" }
|
|
|
|
var foo: Foo
|
|
|
|
func test() {}
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
struct Test {
|
|
var foo: Foo
|
|
|
|
var bar: Bar { "Bar" }
|
|
|
|
func test() {}
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations, exclude: [.wrapPropertyBodies]
|
|
)
|
|
}
|
|
|
|
func testIssue1907() {
|
|
let input = """
|
|
public final class Test: ObservableObject {
|
|
var someProperty: Int? = 0
|
|
|
|
// MARK: - Public -
|
|
|
|
public func somePublicFunction() {
|
|
print("Hello")
|
|
print("Hello")
|
|
print("Hello")
|
|
print("Hello")
|
|
print("Hello")
|
|
}
|
|
|
|
// MARK: - Internal -
|
|
|
|
func someInternalFunction() {
|
|
guard let someProperty else {
|
|
return
|
|
}
|
|
|
|
print("Hello")
|
|
print("Hello")
|
|
print("Hello")
|
|
print("Hello")
|
|
print("Hello")
|
|
}
|
|
|
|
// MARK: - Private -
|
|
|
|
private func somePrivateFunction() {
|
|
print("Hello")
|
|
print("Hello")
|
|
}
|
|
}
|
|
"""
|
|
|
|
let options = FormatOptions(
|
|
categoryMarkComment: "MARK: - %c -",
|
|
beforeMarks: ["class", "let", "var"]
|
|
)
|
|
|
|
testFormatting(for: input, rule: .organizeDeclarations, options: options)
|
|
}
|
|
|
|
func testFixesSpacingAfterMarks() {
|
|
let input = """
|
|
class Foo {
|
|
// MARK: Lifecycle
|
|
init() {}
|
|
// MARK: Internal
|
|
let bar = "bar"
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
class Foo {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
init() {}
|
|
|
|
// MARK: Internal
|
|
|
|
let bar = "bar"
|
|
}
|
|
"""
|
|
|
|
testFormatting(for: input, output, rule: .organizeDeclarations, exclude: [.blankLinesAtStartOfScope])
|
|
}
|
|
|
|
func testRemovesUnnecessaryMark() {
|
|
let input = """
|
|
class Foo {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
init() {}
|
|
|
|
// MARK: Internal
|
|
|
|
// MARK: Internal
|
|
|
|
let bar = "bar"
|
|
|
|
// MARK: Internal
|
|
|
|
let baaz = "baaz"
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
class Foo {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
init() {}
|
|
|
|
// MARK: Internal
|
|
|
|
let bar = "bar"
|
|
|
|
let baaz = "baaz"
|
|
}
|
|
"""
|
|
|
|
testFormatting(for: input, output, rule: .organizeDeclarations, exclude: [.blankLinesAtStartOfScope])
|
|
}
|
|
|
|
func testPreservesUnrelatedComments() {
|
|
let input = """
|
|
enum Test {
|
|
/// Test Properties
|
|
static let foo = "foo"
|
|
static let bar = "bar"
|
|
static let baaz = "baaz"
|
|
}
|
|
"""
|
|
|
|
testFormatting(for: input, rule: .organizeDeclarations)
|
|
}
|
|
|
|
func testNoCrashWhenSortingNestedTypeDeclarations1() {
|
|
let input = """
|
|
public struct MyType {
|
|
var foo: Foo {
|
|
.foo
|
|
}
|
|
|
|
public let a: A
|
|
public let b: B
|
|
public let c: C
|
|
public let d: D
|
|
public let e: E
|
|
|
|
public enum Foo {
|
|
case foo
|
|
case bar
|
|
case baaz
|
|
}
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
public struct MyType {
|
|
|
|
// MARK: Public
|
|
|
|
public enum Foo {
|
|
case foo
|
|
case bar
|
|
case baaz
|
|
}
|
|
|
|
public let a: A
|
|
public let b: B
|
|
public let c: C
|
|
public let d: D
|
|
public let e: E
|
|
|
|
// MARK: Internal
|
|
|
|
var foo: Foo {
|
|
.foo
|
|
}
|
|
|
|
}
|
|
"""
|
|
|
|
let options = FormatOptions(organizeStructThreshold: 0)
|
|
testFormatting(for: input, output, rule: .organizeDeclarations, options: options, exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope])
|
|
}
|
|
|
|
func testNoCrashWhenSortingNestedTypeDeclarations2() {
|
|
let input = """
|
|
public struct MyType {
|
|
public let a: A
|
|
public let b: B
|
|
public let c: C
|
|
public let d: D
|
|
public let e: E
|
|
|
|
public enum Foo {
|
|
case foo
|
|
case bar
|
|
case baaz
|
|
}
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
public struct MyType {
|
|
public enum Foo {
|
|
case foo
|
|
case bar
|
|
case baaz
|
|
}
|
|
|
|
public let a: A
|
|
public let b: B
|
|
public let c: C
|
|
public let d: D
|
|
public let e: E
|
|
|
|
}
|
|
"""
|
|
|
|
let options = FormatOptions(organizeStructThreshold: 0)
|
|
testFormatting(for: input, output, rule: .organizeDeclarations, options: options, exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope])
|
|
}
|
|
|
|
func testSortsMultipleLayersOfNestedTypes() {
|
|
let input = """
|
|
public struct MyType {
|
|
public let a: A
|
|
public let b: B
|
|
public let c: C
|
|
public let d: D
|
|
public let e: E
|
|
|
|
public class Foo {
|
|
class Baaz {
|
|
let b: B
|
|
public let a: A
|
|
|
|
public class Quux {
|
|
let b: B
|
|
public let a: A
|
|
}
|
|
}
|
|
|
|
let bar: Bar
|
|
let baaz: Baaz
|
|
|
|
public class Bar {
|
|
let b: B
|
|
public let a: A
|
|
}
|
|
}
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
public struct MyType {
|
|
public class Foo {
|
|
|
|
// MARK: Public
|
|
|
|
public class Bar {
|
|
|
|
// MARK: Public
|
|
|
|
public let a: A
|
|
|
|
// MARK: Internal
|
|
|
|
let b: B
|
|
}
|
|
|
|
// MARK: Internal
|
|
|
|
class Baaz {
|
|
|
|
// MARK: Public
|
|
|
|
public class Quux {
|
|
|
|
// MARK: Public
|
|
|
|
public let a: A
|
|
|
|
// MARK: Internal
|
|
|
|
let b: B
|
|
}
|
|
|
|
public let a: A
|
|
|
|
// MARK: Internal
|
|
|
|
let b: B
|
|
|
|
}
|
|
|
|
let bar: Bar
|
|
let baaz: Baaz
|
|
|
|
}
|
|
|
|
public let a: A
|
|
public let b: B
|
|
public let c: C
|
|
public let d: D
|
|
public let e: E
|
|
|
|
}
|
|
"""
|
|
|
|
testFormatting(for: input, output, rule: .organizeDeclarations, exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope, .redundantPublic])
|
|
}
|
|
|
|
func testOrganizeDeclarationsSortsEnumNamespace() {
|
|
let input = """
|
|
// swiftformat:sort
|
|
public enum Constants {
|
|
public static let foo = "foo"
|
|
public static let bar = "bar"
|
|
public static let baaz = "baaz"
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
// swiftformat:sort
|
|
public enum Constants {
|
|
public static let baaz = "baaz"
|
|
public static let bar = "bar"
|
|
public static let foo = "foo"
|
|
}
|
|
"""
|
|
|
|
testFormatting(for: input, [output], rules: [.organizeDeclarations, .sortDeclarations])
|
|
}
|
|
|
|
func testIssue2045() {
|
|
let input = """
|
|
public final class A {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
public init(a _: Int) {}
|
|
|
|
convenience init() {
|
|
self.init(a: 0)
|
|
}
|
|
|
|
// MARK: Public
|
|
|
|
public func a() {}
|
|
|
|
// MARK: Private
|
|
|
|
private enum Error: Swift.Error {
|
|
case e
|
|
}
|
|
|
|
private let a1: Float = 0
|
|
private lazy var b: String? = ""
|
|
private let a2 = 0
|
|
|
|
private lazy var x: [Any] =
|
|
if let b {
|
|
[b]
|
|
} else if false {
|
|
[]
|
|
} else {
|
|
[1, 2]
|
|
}
|
|
|
|
private lazy var y = f()
|
|
|
|
private var z: Set<String> = []
|
|
}
|
|
|
|
func f() -> Int { 0 }
|
|
"""
|
|
|
|
let options = FormatOptions(indent: " ")
|
|
testFormatting(for: input, rule: .organizeDeclarations, options: options, exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope, .wrapFunctionBodies])
|
|
}
|
|
|
|
func testOrganizesProtocol() {
|
|
let input = """
|
|
protocol Foo {
|
|
func foo()
|
|
var bar: Bar { get }
|
|
func baaz()
|
|
associatedtype Baaz
|
|
var quux: Quux { get set }
|
|
associatedtype Quux
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
protocol Foo {
|
|
associatedtype Baaz
|
|
associatedtype Quux
|
|
|
|
var bar: Bar { get }
|
|
var quux: Quux { get set }
|
|
|
|
func foo()
|
|
func baaz()
|
|
}
|
|
"""
|
|
|
|
let options = FormatOptions(organizeTypes: ["protocol"])
|
|
testFormatting(for: input, output, rule: .organizeDeclarations, options: options)
|
|
}
|
|
|
|
func testOrganizesProtocolWithInit() {
|
|
let input = """
|
|
public protocol Foo {
|
|
func foo()
|
|
func bar()
|
|
init()
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
public protocol Foo {
|
|
init()
|
|
|
|
func foo()
|
|
func bar()
|
|
}
|
|
"""
|
|
|
|
let options = FormatOptions(organizeTypes: ["protocol"])
|
|
testFormatting(for: input, output, rule: .organizeDeclarations, options: options)
|
|
}
|
|
|
|
func testBelowCustomStructMarkThreshold() {
|
|
let input = """
|
|
struct SmallStruct {
|
|
func foo() {}
|
|
let a = 1
|
|
private let b = 2
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
struct SmallStruct {
|
|
let a = 1
|
|
|
|
func foo() {}
|
|
|
|
private let b = 2
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
options: FormatOptions(markStructThreshold: 20),
|
|
exclude: [.blankLinesAtStartOfScope]
|
|
)
|
|
}
|
|
|
|
func testOrganizedStructNowOverMarkThreshold() {
|
|
let input = """
|
|
struct SmallStruct {
|
|
func foo() {}
|
|
let a = 1
|
|
private let b = 2
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
struct SmallStruct {
|
|
|
|
// MARK: Internal
|
|
|
|
let a = 1
|
|
|
|
func foo() {}
|
|
|
|
// MARK: Private
|
|
|
|
private let b = 2
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
options: FormatOptions(markStructThreshold: 4),
|
|
exclude: [.blankLinesAtStartOfScope]
|
|
)
|
|
}
|
|
|
|
func testBelowCustomStructMarkThresholdDoesntRemoveMarks() {
|
|
let input = """
|
|
struct SmallStruct {
|
|
|
|
// MARK: Internal
|
|
|
|
let a = 1
|
|
|
|
func foo() {}
|
|
|
|
// MARK: Private
|
|
|
|
private let b = 2
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input,
|
|
rule: .organizeDeclarations,
|
|
options: FormatOptions(markStructThreshold: 20),
|
|
exclude: [.blankLinesAtStartOfScope]
|
|
)
|
|
}
|
|
|
|
func testAboveCustomStructMarkThreshold() {
|
|
let input = """
|
|
public struct LargeStruct {
|
|
let a = 1
|
|
let b = 2
|
|
let c = 3
|
|
public func foo() {}
|
|
public func bar() {}
|
|
public func baz() {}
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
public struct LargeStruct {
|
|
|
|
// MARK: Public
|
|
|
|
public func foo() {}
|
|
public func bar() {}
|
|
public func baz() {}
|
|
|
|
// MARK: Internal
|
|
|
|
let a = 1
|
|
let b = 2
|
|
let c = 3
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
options: FormatOptions(markStructThreshold: 5),
|
|
exclude: [.blankLinesAtStartOfScope]
|
|
)
|
|
}
|
|
|
|
func testTypeBodyMarksPreserved() {
|
|
let input = """
|
|
class Foo {
|
|
|
|
// MARK: Unexpected comment
|
|
|
|
var bar: String = "bar"
|
|
|
|
// MARK: Some other comment
|
|
|
|
func baz() {}
|
|
|
|
// MARK: Lifecycle
|
|
init() {}
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
class Foo {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
init() {}
|
|
|
|
// MARK: Internal
|
|
|
|
// MARK: Unexpected comment
|
|
|
|
var bar: String = "bar"
|
|
|
|
// MARK: Some other comment
|
|
|
|
func baz() {}
|
|
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
options: FormatOptions(typeBodyMarks: .preserve),
|
|
exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope, .blankLinesAroundMark]
|
|
)
|
|
}
|
|
|
|
func testTypeBodyMarksRemoved() {
|
|
let input = """
|
|
class Foo {
|
|
|
|
// MARK: Unexpected comment
|
|
|
|
var bar: String = "bar"
|
|
|
|
// MARK: Some other comment
|
|
|
|
func baz() {}
|
|
|
|
// MARK: Lifecycle
|
|
|
|
init() {}
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
class Foo {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
init() {}
|
|
|
|
// MARK: Internal
|
|
|
|
var bar: String = "bar"
|
|
|
|
func baz() {}
|
|
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
options: FormatOptions(typeBodyMarks: .remove),
|
|
exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope]
|
|
)
|
|
}
|
|
|
|
func testTypeBodyMarksPreserveValidMarks() {
|
|
let input = """
|
|
class Foo {
|
|
|
|
// MARK: Some unexpected comment
|
|
|
|
var bar: String = "bar"
|
|
|
|
// MARK: Internal
|
|
|
|
func validComment() {}
|
|
|
|
init() {}
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
class Foo {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
init() {}
|
|
|
|
// MARK: Internal
|
|
|
|
var bar: String = "bar"
|
|
|
|
func validComment() {}
|
|
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
options: FormatOptions(typeBodyMarks: .remove),
|
|
exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope]
|
|
)
|
|
}
|
|
|
|
func testTypeBodyMarksWithTypeMode() {
|
|
let input = """
|
|
class Foo {
|
|
|
|
// MARK: Unexpected section
|
|
|
|
var bar: String = "bar"
|
|
|
|
// MARK: Not a function category
|
|
func baz() {}
|
|
|
|
init() {}
|
|
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
class Foo {
|
|
|
|
// MARK: Properties
|
|
|
|
var bar: String = "bar"
|
|
|
|
// MARK: Lifecycle
|
|
|
|
init() {}
|
|
|
|
// MARK: Functions
|
|
|
|
func baz() {}
|
|
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
options: FormatOptions(
|
|
categoryMarkComment: "MARK: %c",
|
|
organizationMode: .type,
|
|
typeBodyMarks: .remove
|
|
),
|
|
exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope]
|
|
)
|
|
}
|
|
|
|
func testRemovesAllUnnecessaryMarkAfterStandardMark() {
|
|
let input = """
|
|
public class Foo {
|
|
|
|
// MARK: Public
|
|
|
|
public func bar() {}
|
|
|
|
// MARK: Internal
|
|
|
|
// MARK: Implementation
|
|
|
|
func method() {}
|
|
|
|
// MARK: Testing
|
|
|
|
func testMethod() {}
|
|
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
public class Foo {
|
|
|
|
// MARK: Public
|
|
|
|
public func bar() {}
|
|
|
|
// MARK: Internal
|
|
|
|
func method() {}
|
|
|
|
func testMethod() {}
|
|
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
options: FormatOptions(typeBodyMarks: .remove),
|
|
exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope]
|
|
)
|
|
}
|
|
|
|
func testOrganizesProtocolWithAsync() {
|
|
// Async variables are not allowed in protocols
|
|
let input = """
|
|
protocol Foo {
|
|
func foo() async
|
|
var bar: Bar { get }
|
|
|
|
func baaz()
|
|
async
|
|
var quux: Quux { get }
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
protocol Foo {
|
|
var bar: Bar { get }
|
|
|
|
var quux: Quux { get }
|
|
|
|
func foo() async
|
|
func baaz()
|
|
async
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
options: FormatOptions(organizeTypes: ["protocol"]),
|
|
exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope]
|
|
)
|
|
}
|
|
|
|
func testHandlesMalformedPropertyType() {
|
|
let input = """
|
|
extension Foo {
|
|
/// Invalid type, should still get handled properly
|
|
private var foo: FooBar++ {
|
|
guard
|
|
let foo = foo.bar,
|
|
let bar = foo.bar
|
|
else {
|
|
return nil
|
|
}
|
|
|
|
return bar
|
|
}
|
|
}
|
|
|
|
extension Foo {
|
|
/// Invalid type, should still get handled properly
|
|
func foo() -> FooBar++ {
|
|
guard
|
|
let foo = foo.bar,
|
|
let bar = foo.bar
|
|
else {
|
|
return nil
|
|
}
|
|
|
|
return bar
|
|
}
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input,
|
|
rule: .organizeDeclarations,
|
|
options: FormatOptions(organizeTypes: ["extension"]),
|
|
exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope]
|
|
)
|
|
}
|
|
|
|
func testMovesInternalPropertyOutOfPrivateSection() {
|
|
// Internal property `placement` should be moved from Private section to Internal section
|
|
let input = """
|
|
private struct Foo: View {
|
|
|
|
// MARK: Internal
|
|
|
|
var body: some View {
|
|
EmptyView()
|
|
}
|
|
|
|
// MARK: Private
|
|
|
|
@Environment(\\.bar) private var bar
|
|
@Environment(\\.baz) private var baz
|
|
|
|
let placement: Placement
|
|
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
private struct Foo: View {
|
|
|
|
// MARK: Internal
|
|
|
|
let placement: Placement
|
|
|
|
var body: some View {
|
|
EmptyView()
|
|
}
|
|
|
|
// MARK: Private
|
|
|
|
@Environment(\\.bar) private var bar
|
|
@Environment(\\.baz) private var baz
|
|
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope]
|
|
)
|
|
}
|
|
|
|
func testPrivateVarWithDefaultValuePreventsReordering() {
|
|
// private var with default value is still part of memberwise init (optional param),
|
|
// so reordering stored properties would break the init.
|
|
// Section headers can be added, but the order must be preserved (bar before baz).
|
|
let input = """
|
|
struct Foo {
|
|
let bar: Bar
|
|
private var baz = Baz()
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
struct Foo {
|
|
|
|
// MARK: Internal
|
|
|
|
let bar: Bar
|
|
|
|
// MARK: Private
|
|
|
|
private var baz = Baz()
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope, .propertyTypes]
|
|
)
|
|
}
|
|
|
|
func testPrivateLetWithDefaultValueAllowsReordering() {
|
|
// `private let` with default value, or `@State private var` with default value,
|
|
// is NOT part of memberwise init so it can be freely reordered (baz moves after bar)
|
|
let input = """
|
|
struct Foo {
|
|
private let baz = Baz()
|
|
@State private var foo: Foo?
|
|
let bar: Bar
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
struct Foo {
|
|
|
|
// MARK: Internal
|
|
|
|
let bar: Bar
|
|
|
|
// MARK: Private
|
|
|
|
@State private var foo: Foo?
|
|
|
|
private let baz = Baz()
|
|
}
|
|
"""
|
|
|
|
testFormatting(
|
|
for: input, output,
|
|
rule: .organizeDeclarations,
|
|
exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope, .propertyTypes]
|
|
)
|
|
}
|
|
|
|
func testNoCrashWithTrailingCommentOnOpeningBrace() {
|
|
// Regression test: this previously caused an infinite loop / timeout because
|
|
// inserting a linebreak at `openBraceIndex + 1` when there is trailing content
|
|
// on the same line as `{` never shifted `body[0].range.lowerBound`.
|
|
let input = """
|
|
struct Foo { // some comment
|
|
let x: Int
|
|
let y: String
|
|
private func bar() {}
|
|
}
|
|
"""
|
|
|
|
let output = """
|
|
struct Foo {
|
|
|
|
// MARK: Internal
|
|
|
|
// some comment
|
|
let x: Int
|
|
let y: String
|
|
|
|
// MARK: Private
|
|
|
|
private func bar() {}
|
|
}
|
|
"""
|
|
|
|
let output2 = """
|
|
struct Foo {
|
|
|
|
// MARK: Internal
|
|
|
|
// some comment
|
|
let x: Int
|
|
let y: String
|
|
|
|
// MARK: Private
|
|
|
|
private func bar() {}
|
|
}
|
|
"""
|
|
|
|
let options = FormatOptions(organizeStructThreshold: 0)
|
|
testFormatting(
|
|
for: input, [output, output2],
|
|
rules: [.organizeDeclarations],
|
|
options: options,
|
|
exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope]
|
|
)
|
|
}
|
|
}
|