Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix issue where type under organizeDeclarations line count threshold would ignore swiftformat:sort directive #1976

Merged
merged 1 commit into from
Feb 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions Sources/FormattingHelpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1682,6 +1682,37 @@
return last(.keyword, before: startOfScopeIndex) == .keyword("func")
}
}

/// Whether or not the length of the type at the given index exceeds the minimum threshold to be organized
func typeLengthExceedsOrganizationThreshold(at typeKeywordIndex: Int) -> Bool {
let organizationThreshold: Int
switch tokens[typeKeywordIndex].string {
case "class", "actor":
organizationThreshold = options.organizeClassThreshold
case "struct":
organizationThreshold = options.organizeStructThreshold
case "enum":
organizationThreshold = options.organizeEnumThreshold
case "extension":
organizationThreshold = options.organizeExtensionThreshold
default:
organizationThreshold = 0

Check warning on line 1699 in Sources/FormattingHelpers.swift

View check run for this annotation

Codecov / codecov/patch

Sources/FormattingHelpers.swift#L1699

Added line #L1699 was not covered by tests
}

guard organizationThreshold != 0,
let startOfScope = index(of: .startOfScope("{"), after: typeKeywordIndex),
let endOfScope = endOfScope(at: startOfScope)
else {
return true
}

let lineCount = tokens[startOfScope ... endOfScope]
.filter(\.isLinebreak)
.count
- 1

return lineCount >= organizationThreshold
}
}

extension Formatter {
Expand Down
32 changes: 2 additions & 30 deletions Sources/Rules/OrganizeDeclarations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public extension FormatRule {
"visibilityorder", "typeorder", "visibilitymarks", "typemarks",
"groupblanklines", "sortswiftuiprops",
],
sharedOptions: ["sortedpatterns", "lineaftermarks"]
sharedOptions: ["sortedpatterns", "lineaftermarks", "linebreaks"]
) { formatter in
guard !formatter.options.fragment else { return }

Expand Down Expand Up @@ -146,7 +146,7 @@ extension Formatter {
func organizeDeclaration(_ typeDeclaration: TypeDeclaration) {
guard !typeDeclaration.body.isEmpty,
options.organizeTypes.contains(typeDeclaration.keyword),
typeLengthExceedsOrganizationThreshold(typeDeclaration)
typeLengthExceedsOrganizationThreshold(at: typeDeclaration.keywordIndex)
else { return }

// Parse category order from options
Expand Down Expand Up @@ -191,34 +191,6 @@ extension Formatter {
)
}

/// Whether or not the length of this types exceeds the minimum threshold to be organized
func typeLengthExceedsOrganizationThreshold(_ typeDeclaration: TypeDeclaration) -> Bool {
let organizationThreshold: Int
switch typeDeclaration.keyword {
case "class", "actor":
organizationThreshold = options.organizeClassThreshold
case "struct":
organizationThreshold = options.organizeStructThreshold
case "enum":
organizationThreshold = options.organizeEnumThreshold
case "extension":
organizationThreshold = options.organizeExtensionThreshold
default:
organizationThreshold = 0
}

guard organizationThreshold != 0 else {
return true
}

let lineCount = typeDeclaration.body
.flatMap(\.tokens)
.filter(\.isLinebreak)
.count

return lineCount >= organizationThreshold
}

typealias CategorizedDeclaration = (declaration: Declaration, category: Category)

/// Sorts the given categorized declarations based on the defined category ordering
Expand Down
9 changes: 5 additions & 4 deletions Sources/Rules/SortDeclarations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public extension FormatRule {
// swiftformat:sort:end comments.
""",
options: ["sortedpatterns"],
sharedOptions: ["organizetypes", "linebreaks"]
sharedOptions: ["linebreaks", "organizetypes", "structthreshold", "classthreshold", "enumthreshold", "extensionlength"]
) { formatter in
formatter.forEachToken(
where: {
Expand Down Expand Up @@ -57,15 +57,16 @@ public extension FormatRule {
let typeCloseBrace = formatter.endOfScope(at: typeOpenBrace),
let firstTypeBodyToken = formatter.index(of: .nonLinebreak, after: typeOpenBrace),
let lastTypeBodyToken = formatter.index(of: .nonLinebreak, before: typeCloseBrace),
let declarationKeyword = formatter.lastSignificantKeyword(at: typeOpenBrace),
let declarationKeywordIndex = formatter.indexOfLastSignificantKeyword(at: typeOpenBrace),
lastTypeBodyToken > typeOpenBrace
else { return }

// Sorting the body of a type conflicts with the `organizeDeclarations`
// keyword if enabled for this type of declaration. In that case,
// keyword if enabled for this declaration. In that case,
// defer to the sorting implementation in `organizeDeclarations`.
if formatter.options.enabledRules.contains(FormatRule.organizeDeclarations.name),
formatter.options.organizeTypes.contains(declarationKeyword)
formatter.options.organizeTypes.contains(formatter.tokens[declarationKeywordIndex].string),
formatter.typeLengthExceedsOrganizationThreshold(at: declarationKeywordIndex)
{
return
}
Expand Down
13 changes: 7 additions & 6 deletions Tests/MetadataTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -155,12 +155,6 @@ class MetadataTests: XCTestCase {
allSharedOptions.subtract(ruleOptions)
var referencedOptions = [OptionDescriptor]()
for index in scopeStart + 1 ..< formatter.tokens.count {
guard (formatter.token(at: index - 1) == .operator(".", .infix)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These checks were filtering out the typeLengthExceedsOrganizationThreshold call in the organizeDeclarations rule, since it's in an extension Formatter and uses implicit self. Since we can remove this without getting any new unexpected issues, that seems reasonable.

&& formatter.token(at: index - 2) == .identifier("formatter"))
|| (formatter.token(at: index) == .identifier("options") && formatter.token(at: index - 1)?.isOperator(".") == false)
else {
continue
}
switch formatter.tokens[index] {
// Find all of the options called via `options.optionName`
case .identifier("options") where formatter.token(at: index + 1) == .operator(".", .infix):
Expand Down Expand Up @@ -205,6 +199,13 @@ class MetadataTests: XCTestCase {
referencedOptions += [
Descriptors.selfRequired,
]
case .identifier("typeLengthExceedsOrganizationThreshold"):
referencedOptions += [
Descriptors.organizeClassThreshold,
Descriptors.organizeStructThreshold,
Descriptors.organizeEnumThreshold,
Descriptors.organizeExtensionThreshold,
]
default:
continue
}
Expand Down
22 changes: 22 additions & 0 deletions Tests/Rules/OrganizeDeclarationsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3785,4 +3785,26 @@ class OrganizeDeclarationsTests: XCTestCase {

testFormatting(for: input, output, rule: .organizeDeclarations, exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope])
}

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])
}
}
46 changes: 46 additions & 0 deletions Tests/Rules/SortDeclarationsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -286,4 +286,50 @@ class SortDeclarationsTests: XCTestCase {

testFormatting(for: input, rule: .sortDeclarations)
}

func testSortEnumNamespaceSmallerThanOrganizeDeclarationsEnumThreshold() {
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"
}
"""

let options = FormatOptions(organizeEnumThreshold: 20)
testFormatting(for: input, [output], rules: [.sortDeclarations, .organizeDeclarations], options: options)
}

func testSortStructSmallerThanOrganizeDeclarationsEnumThreshold() {
let input = """
// swiftformat:sort
public struct Foo {
public let foo = "foo"
public let bar = "bar"
public let baaz = "baaz"
}
"""

let output = """
// swiftformat:sort
public struct Foo {
public let baaz = "baaz"
public let bar = "bar"
public let foo = "foo"
}
"""

let options = FormatOptions(organizeStructThreshold: 20)
testFormatting(for: input, [output], rules: [.sortDeclarations, .organizeDeclarations])
}
}