Skip to content

Commit

Permalink
Add SwiftUI properties subcategory alphabetical sort (#1794)
Browse files Browse the repository at this point in the history
  • Loading branch information
miguel-jimenez-0529 authored Jul 31, 2024
1 parent 8525076 commit 410de98
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 3 deletions.
1 change: 1 addition & 0 deletions Rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -1410,6 +1410,7 @@ Option | Description
`--visibilitymarks` | Marks for visibility groups (public:Public Fields,..)
`--typemarks` | Marks for declaration type groups (classMethod:Baaz,..)
`--groupblanklines` | Require a blank line after each subgroup. Default: true
`--sortswiftuiprops` | Sorts SwiftUI properties alphabetically, defaults to "false"

<details>
<summary>Examples</summary>
Expand Down
8 changes: 6 additions & 2 deletions Sources/DeclarationHelpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,10 @@ enum Declaration: Hashable {
})
return allModifiers
}

var swiftUIPropertyWrapper: String? {
modifiers.first(where: Declaration.swiftUIPropertyWrappers.contains)
}
}

extension Formatter {
Expand Down Expand Up @@ -530,7 +534,7 @@ extension Declaration {

let isSwiftUIPropertyWrapper = declarationParser
.modifiersForDeclaration(at: declarationTypeTokenIndex) { _, modifier in
swiftUIPropertyWrappers.contains(modifier)
Declaration.swiftUIPropertyWrappers.contains(modifier)
}

switch declarationTypeToken {
Expand Down Expand Up @@ -608,7 +612,7 @@ extension Declaration {

/// Represents all the native SwiftUI property wrappers that conform to `DynamicProperty` and cause a SwiftUI view to re-render.
/// Most of these are listed here: https://developer.apple.com/documentation/swiftui/dynamicproperty
private var swiftUIPropertyWrappers: Set<String> {
fileprivate static var swiftUIPropertyWrappers: Set<String> {
[
"@AccessibilityFocusState",
"@AppStorage",
Expand Down
9 changes: 9 additions & 0 deletions Sources/OptionDescriptor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1200,6 +1200,15 @@ struct _Descriptors {
keyPath: \.preservedPrivateDeclarations
)

let alphabetizeSwiftUIPropertyTypes = OptionDescriptor(
argumentName: "sortswiftuiprops",
displayName: "Alphabetize SwiftUI Properties",
help: "Sorts SwiftUI properties alphabetically, defaults to \"false\"",
keyPath: \.alphabetizeSwiftUIPropertyTypes,
trueValues: ["enabled", "true"],
falseValues: ["disabled", "false"]
)

// MARK: - Internal

let fragment = OptionDescriptor(
Expand Down
3 changes: 3 additions & 0 deletions Sources/Options.swift
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,7 @@ public struct FormatOptions: CustomStringConvertible {
public var customTypeMarks: Set<String>
public var blankLineAfterSubgroups: Bool
public var alphabeticallySortedDeclarationPatterns: Set<String>
public var alphabetizeSwiftUIPropertyTypes: Bool
public var yodaSwap: YodaMode
public var extensionACLPlacement: ExtensionACLPlacement
public var redundantType: RedundantType
Expand Down Expand Up @@ -798,6 +799,7 @@ public struct FormatOptions: CustomStringConvertible {
customTypeMarks: Set<String> = [],
blankLineAfterSubgroups: Bool = true,
alphabeticallySortedDeclarationPatterns: Set<String> = [],
alphabetizeSwiftUIPropertyTypes: Bool = false,
yodaSwap: YodaMode = .always,
extensionACLPlacement: ExtensionACLPlacement = .onExtension,
redundantType: RedundantType = .inferLocalsOnly,
Expand Down Expand Up @@ -913,6 +915,7 @@ public struct FormatOptions: CustomStringConvertible {
self.customTypeMarks = customTypeMarks
self.blankLineAfterSubgroups = blankLineAfterSubgroups
self.alphabeticallySortedDeclarationPatterns = alphabeticallySortedDeclarationPatterns
self.alphabetizeSwiftUIPropertyTypes = alphabetizeSwiftUIPropertyTypes
self.yodaSwap = yodaSwap
self.extensionACLPlacement = extensionACLPlacement
self.redundantType = redundantType
Expand Down
10 changes: 9 additions & 1 deletion Sources/Rules/OrganizeDeclarations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public extension FormatRule {
"lifecycle", "organizetypes", "structthreshold", "classthreshold",
"enumthreshold", "extensionlength", "organizationmode",
"visibilityorder", "typeorder", "visibilitymarks", "typemarks",
"groupblanklines",
"groupblanklines", "sortswiftuiprops",
],
sharedOptions: ["sortedpatterns", "lineaftermarks"]
) { formatter in
Expand Down Expand Up @@ -217,6 +217,14 @@ extension Formatter {
return lhsName.localizedCompare(rhsName) == .orderedAscending
}

if options.alphabetizeSwiftUIPropertyTypes,
lhs.category.type == rhs.category.type,
let lhsSwiftUIProperty = lhs.declaration.swiftUIPropertyWrapper,
let rhsSwiftUIProperty = rhs.declaration.swiftUIPropertyWrapper
{
return lhsSwiftUIProperty.localizedCompare(rhsSwiftUIProperty) == .orderedAscending
}

// Respect the original declaration ordering when the categories and types are the same
return lhsOriginalIndex < rhsOriginalIndex
})
Expand Down
1 change: 1 addition & 0 deletions Tests/MetadataTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ class MetadataTests: XCTestCase {
Descriptors.customVisibilityMarks,
Descriptors.customTypeMarks,
Descriptors.blankLineAfterSubgroups,
Descriptors.alphabetizeSwiftUIPropertyTypes,
]
case .identifier("removeSelf"):
referencedOptions += [
Expand Down
108 changes: 108 additions & 0 deletions Tests/Rules/OrganizeDeclarationsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3005,6 +3005,114 @@ class OrganizeDeclarationsTests: XCTestCase {
)
}

func testSortSwiftUIPropertyWrappersSubCategory() {
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
@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,
alphabetizeSwiftUIPropertyTypes: true
),
exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope]
)
}

func testSortSwiftUIWrappersByTypeAndMaintainGroupSpacing() {
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,
alphabetizeSwiftUIPropertyTypes: true
),
exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope]
)
}

func testPreservesBlockOfConsecutivePropertiesWithoutBlankLinesBetweenSubgroups1() {
let input = """
class Foo {
Expand Down

0 comments on commit 410de98

Please sign in to comment.