Skip to content

Commit

Permalink
Fix file sorted by name but not by path
Browse files Browse the repository at this point in the history
  • Loading branch information
mattmassicotte committed Jul 10, 2024
1 parent c83bad1 commit ed0c8e7
Show file tree
Hide file tree
Showing 6 changed files with 269 additions and 33 deletions.
68 changes: 38 additions & 30 deletions Sources/XCLinting/Rules/GroupsAreSortedRule.swift
Original file line number Diff line number Diff line change
@@ -1,38 +1,46 @@
import Foundation
import XcodeProj

func groupsAreSortedRule(_ environment: XCLinter.Environment) -> [Violation] {
var violations = [Violation]()
for group in environment.project.pbxproj.groups {
violations.append(contentsOf: validateGroupIsSorted(group))
struct GroupsAreSortedRule {
func run(_ environment: XCLinter.Environment) throws -> [Violation] {
var violations = [Violation]()
for group in environment.project.pbxproj.groups {
violations.append(contentsOf: validateGroupIsSorted(group))
}
return violations
}
return violations
}

private func validateGroupIsSorted(_ group: PBXGroup) -> [Violation] {
var violations = [Violation]()
let children = group.children.compactMap(\.path)
let sortedChildren = children.sorted { lhs, rhs in
lhs.compare(
rhs,
options: [
.numeric,
.caseInsensitive,
.widthInsensitive,
.forcedOrdering
],
locale: .current
) == .orderedAscending
}

// some groups have no path, like the auto-generated "Products". Let's skip those, as they appear to not even always show up in the UI.
if children != sortedChildren, let path = group.path {
violations.append(.init("Group \"\(path)\" contains unsorted children"))
}
private func validateGroupIsSorted(_ group: PBXGroup) -> [Violation] {
var violations = [Violation]()

for childGroup in group.children.compactMap({ $0 as? PBXGroup }) {
violations.append(contentsOf: validateGroupIsSorted(childGroup))
}
// a path can contain components, but only the last matters from the UI's perspective
let children = group.children
.compactMap(\.path)
.map { $0.split(separator: "/").last }
.compactMap { $0 }

return violations
let sortedChildren = children.sorted { lhs, rhs in
lhs.compare(
rhs,
options: [
.numeric,
.caseInsensitive,
.widthInsensitive,
.forcedOrdering
],
locale: .current
) == .orderedAscending
}

// some groups have no path, like the auto-generated "Products". Let's skip those, as they appear to not even always show up in the UI.
if children != sortedChildren, let path = group.path {
violations.append(.init("Group \"\(path)\" contains unsorted children"))
}

for childGroup in group.children.compactMap({ $0 as? PBXGroup }) {
violations.append(contentsOf: validateGroupIsSorted(childGroup))
}

return violations
}
}
2 changes: 1 addition & 1 deletion Sources/XCLinting/XCLinter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ extension XCLinter {
public static let ruleMap: [String: Rule] = [
"embedded_build_setting": { try EmbeddedBuildSettingsRule().run($0) },
"build_files_ordered": { try BuildFilesAreOrderedRule().run($0) },
"groups_sorted": { groupsAreSortedRule($0) },
"groups_sorted": { try GroupsAreSortedRule().run($0) },
"validate_build_settings": { try ValidateBuildSettingsRule().run($0) },
"implicit_dependencies": { try ImplicitDependenciesRule().run($0) },
"targets_use_xcconfig": { try TargetsUseXCConfigRule().run($0) },
Expand Down
19 changes: 17 additions & 2 deletions Tests/XCLintTests/GroupsAreSortedRuleTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ final class GroupsAreSortedRuleTests: XCTestCase {
configuration: Configuration()
)

let violations = groupsAreSortedRule(env)
let violations = try GroupsAreSortedRule().run(env)
XCTAssertTrue(violations.isEmpty)
}

Expand All @@ -30,8 +30,23 @@ final class GroupsAreSortedRuleTests: XCTestCase {
configuration: Configuration()
)

let violations = groupsAreSortedRule(env)
let violations = try GroupsAreSortedRule().run(env)
XCTAssertFalse(violations.isEmpty)
}

func testProjectWithoutGroupsSortedByReference() throws {
let url = try Bundle.module.testDataURL(named: "SortedGroupsByReference.xcodeproj")

let project = try XcodeProj(pathString: url.path)

let env = XCLinter.Environment(
project: project,
projectRootURL: url,
configuration: Configuration()
)

let violations = try GroupsAreSortedRule().run(env)
XCTAssertTrue(violations.isEmpty)
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 56;
objects = {

/* Begin PBXBuildFile section */
C965BD2C2AE6E5D700E5836A /* StockMacOSAppApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C965BD2B2AE6E5D700E5836A /* StockMacOSAppApp.swift */; };
C965BD2E2AE6E5D700E5836A /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C965BD2D2AE6E5D700E5836A /* ContentView.swift */; };
C965BD302AE6E5D800E5836A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C965BD2F2AE6E5D800E5836A /* Assets.xcassets */; };
C965BD332AE6E5D800E5836A /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C965BD322AE6E5D800E5836A /* Preview Assets.xcassets */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
C965BD282AE6E5D700E5836A /* .app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = .app; sourceTree = BUILT_PRODUCTS_DIR; };
C965BD2B2AE6E5D700E5836A /* StockMacOSAppApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = StockMacOSAppApp.swift; path = A/StockMacOSAppApp.swift; sourceTree = SOURCE_ROOT; };
C965BD2D2AE6E5D700E5836A /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = SOURCE_ROOT; };
C965BD2F2AE6E5D800E5836A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
C965BD322AE6E5D800E5836A /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
C965BD342AE6E5D800E5836A /* StockMacOSApp.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = StockMacOSApp.entitlements; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
C965BD252AE6E5D700E5836A /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */

/* Begin PBXGroup section */
C965BD1F2AE6E5D700E5836A = {
isa = PBXGroup;
children = (
C965BD2A2AE6E5D700E5836A /* StockMacOSApp */,
C965BD292AE6E5D700E5836A /* Products */,
);
sourceTree = "<group>";
};
C965BD292AE6E5D700E5836A /* Products */ = {
isa = PBXGroup;
children = (
C965BD282AE6E5D700E5836A /* .app */,
);
name = Products;
sourceTree = "<group>";
};
C965BD2A2AE6E5D700E5836A /* StockMacOSApp */ = {
isa = PBXGroup;
children = (
C965BD2F2AE6E5D800E5836A /* Assets.xcassets */,
C965BD2D2AE6E5D700E5836A /* ContentView.swift */,
C965BD312AE6E5D800E5836A /* Preview Content */,
C965BD342AE6E5D800E5836A /* StockMacOSApp.entitlements */,
C965BD2B2AE6E5D700E5836A /* StockMacOSAppApp.swift */,
);
path = StockMacOSApp;
sourceTree = "<group>";
};
C965BD312AE6E5D800E5836A /* Preview Content */ = {
isa = PBXGroup;
children = (
C965BD322AE6E5D800E5836A /* Preview Assets.xcassets */,
);
path = "Preview Content";
sourceTree = "<group>";
};
/* End PBXGroup section */

/* Begin PBXNativeTarget section */
C965BD272AE6E5D700E5836A /* StockMacOSApp */ = {
isa = PBXNativeTarget;
buildConfigurationList = C965BD372AE6E5D800E5836A /* Build configuration list for PBXNativeTarget "StockMacOSApp" */;
buildPhases = (
C965BD242AE6E5D700E5836A /* Sources */,
C965BD252AE6E5D700E5836A /* Frameworks */,
C965BD262AE6E5D700E5836A /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = StockMacOSApp;
productName = StockMacOSApp;
productReference = C965BD282AE6E5D700E5836A /* .app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */

/* Begin PBXProject section */
C965BD202AE6E5D700E5836A /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1510;
LastUpgradeCheck = 1510;
TargetAttributes = {
C965BD272AE6E5D700E5836A = {
CreatedOnToolsVersion = 15.1;
};
};
};
buildConfigurationList = C965BD232AE6E5D700E5836A /* Build configuration list for PBXProject "SortedGroupsByReference" */;
compatibilityVersion = "Xcode 14.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = C965BD1F2AE6E5D700E5836A;
productRefGroup = C965BD292AE6E5D700E5836A /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
C965BD272AE6E5D700E5836A /* StockMacOSApp */,
);
};
/* End PBXProject section */

/* Begin PBXResourcesBuildPhase section */
C965BD262AE6E5D700E5836A /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
C965BD332AE6E5D800E5836A /* Preview Assets.xcassets in Resources */,
C965BD302AE6E5D800E5836A /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */

/* Begin PBXSourcesBuildPhase section */
C965BD242AE6E5D700E5836A /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
C965BD2E2AE6E5D700E5836A /* ContentView.swift in Sources */,
C965BD2C2AE6E5D700E5836A /* StockMacOSAppApp.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */

/* Begin XCBuildConfiguration section */
C965BD352AE6E5D800E5836A /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
};
name = Debug;
};
C965BD362AE6E5D800E5836A /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
};
name = Release;
};
C965BD382AE6E5D800E5836A /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
};
name = Debug;
};
C965BD392AE6E5D800E5836A /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
};
name = Release;
};
/* End XCBuildConfiguration section */

/* Begin XCConfigurationList section */
C965BD232AE6E5D700E5836A /* Build configuration list for PBXProject "SortedGroupsByReference" */ = {
isa = XCConfigurationList;
buildConfigurations = (
C965BD352AE6E5D800E5836A /* Debug */,
C965BD362AE6E5D800E5836A /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
C965BD372AE6E5D800E5836A /* Build configuration list for PBXNativeTarget "StockMacOSApp" */ = {
isa = XCConfigurationList;
buildConfigurations = (
C965BD382AE6E5D800E5836A /* Debug */,
C965BD392AE6E5D800E5836A /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = C965BD202AE6E5D700E5836A /* Project object */;
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

0 comments on commit ed0c8e7

Please sign in to comment.