diff --git a/Package.resolved b/Package.resolved index 101d4e5..d12b253 100644 --- a/Package.resolved +++ b/Package.resolved @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/nicklockwood/SwiftFormat", "state" : { - "revision" : "2d5a2b6bde636c1feae2c852ab9a50f221e98c66", - "version" : "0.55.3" + "revision" : "4e92b81311f528cfdca8015d629c650d0aff94ce", + "version" : "0.55.4" } }, { diff --git a/Sources/PublicModules/PADOutputGenerator/MarkdownOutputGenerator.swift b/Sources/PublicModules/PADOutputGenerator/MarkdownOutputGenerator.swift index 6909e18..a683e62 100644 --- a/Sources/PublicModules/PADOutputGenerator/MarkdownOutputGenerator.swift +++ b/Sources/PublicModules/PADOutputGenerator/MarkdownOutputGenerator.swift @@ -31,6 +31,10 @@ public struct MarkdownOutputGenerator: OutputGenerating { if let oldVersionName, let newVersionName { lines += [Self.repoInfo(oldVersionName: oldVersionName, newVersionName: newVersionName)] } + + if !changes.isEmpty { + lines += [Self.totalChangesBreakdown(changesPerTarget: changesPerTarget)] + } lines += [separator] @@ -62,8 +66,29 @@ private extension MarkdownOutputGenerator { return "# βœ… No changes detected" } - let totalChangeCount = changesPerTarget.totalChangeCount - return "# πŸ‘€ \(totalChangeCount) public \(totalChangeCount == 1 ? "change" : "changes") detected" + let totalChangeCount = changesPerTarget.totalCount(for: .allChanges) + + if changesPerTarget.potentiallyBreakingChangesCount > 0 { + return "# ⚠️ \(totalChangeCount) public \(totalChangeCount == 1 ? "change" : "changes") detected ⚠️" + } else { + return "# πŸ‘€ \(totalChangeCount) public \(totalChangeCount == 1 ? "change" : "changes") detected" + } + } + + static func totalChangesBreakdown(changesPerTarget: [String: [Change]]) -> String { + + let additions = changesPerTarget.totalCount(for: .additions) + let changes = changesPerTarget.totalCount(for: .modifications) + let removals = changesPerTarget.totalCount(for: .removals) + + guard additions + changes + removals > 0 else { return "" } + + var breakdown = "" + if additions > 0 { breakdown += "" } + if changes > 0 { breakdown += "" } + if removals > 0 { breakdown += "" } + breakdown += "
❇️\(additions) \(additions == 1 ? "Addition" : "Additions")
πŸ”€\(changes) \(changes == 1 ? "Modification" : "Modifications")
❌\(removals) \(removals == 1 ? "Removal" : "Removals")
" + return breakdown } static func repoInfo(oldVersionName: String, newVersionName: String) -> String { @@ -106,11 +131,11 @@ private extension MarkdownOutputGenerator { changes: changes.filter(\.changeType.isAddition) ) let changeLines = changeSectionLines( - title: "#### πŸ”€ Changed", - changes: changes.filter(\.changeType.isChange) + title: "#### πŸ”€ Modified", + changes: changes.filter(\.changeType.isModification) ) let removalLines = changeSectionLines( - title: "#### πŸ˜Άβ€πŸŒ«οΈ Removed", + title: "#### ❌ Removed", changes: changes.filter(\.changeType.isRemoval) ) @@ -157,7 +182,7 @@ private extension MarkdownOutputGenerator { return description case let .removal(description): return description - case let .change(before, after): + case let .modification(before, after): return "// From\n\(before)\n\n// To\n\(after)" } } @@ -165,10 +190,30 @@ private extension MarkdownOutputGenerator { private extension [String: [Change]] { - var totalChangeCount: Int { + enum ChangeCountType { + case allChanges + case additions + case modifications + case removals + } + + var potentiallyBreakingChangesCount: Int { + return totalCount(for: .modifications) + totalCount(for: .removals) + } + + func totalCount(for countType: ChangeCountType) -> Int { var totalChangeCount = 0 keys.forEach { targetName in - totalChangeCount += self[targetName]?.count ?? 0 + switch countType { + case .allChanges: + totalChangeCount += self[targetName]?.count ?? 0 + case .additions: + totalChangeCount += self[targetName]?.reduce(0, { $0 + ($1.changeType.isAddition ? 1 : 0) }) ?? 0 + case .modifications: + totalChangeCount += self[targetName]?.reduce(0, { $0 + ($1.changeType.isModification ? 1 : 0) }) ?? 0 + case .removals: + totalChangeCount += self[targetName]?.reduce(0, { $0 + ($1.changeType.isRemoval ? 1 : 0) }) ?? 0 + } } return totalChangeCount } diff --git a/Sources/PublicModules/PADPackageFileAnalyzer/SwiftPackageFileAnalyzer.swift b/Sources/PublicModules/PADPackageFileAnalyzer/SwiftPackageFileAnalyzer.swift index 9d69162..c420728 100644 --- a/Sources/PublicModules/PADPackageFileAnalyzer/SwiftPackageFileAnalyzer.swift +++ b/Sources/PublicModules/PADPackageFileAnalyzer/SwiftPackageFileAnalyzer.swift @@ -124,7 +124,7 @@ private extension SwiftPackageFileAnalyzer { guard let new, let old else { return [] } return [.init( - changeType: .change( + changeType: .modification( oldDescription: "\(keyName): \"\(old)\"", newDescription: "\(keyName): \"\(new)\"" ), @@ -143,7 +143,7 @@ private extension SwiftPackageFileAnalyzer { let keyName = "name" return [.init( - changeType: .change( + changeType: .modification( oldDescription: "\(keyName): \"\(old)\"", newDescription: "\(keyName): \"\(new)\"" ), @@ -191,7 +191,7 @@ private extension SwiftPackageFileAnalyzer { let newPlatformsString = new.map { "\($0.description)" }.joined(separator: ", ") return [.init( - changeType: .change( + changeType: .modification( oldDescription: "platforms: [\(oldPlatformsString)]", newDescription: "platforms: [\(newPlatformsString)]" ), @@ -265,7 +265,7 @@ private extension SwiftPackageFileAnalyzer { listOfChanges += removed.map { "Removed target \"\($0)\"" } return [.init( - changeType: .change( + changeType: .modification( oldDescription: oldProduct.description, newDescription: newProduct.description ), @@ -360,7 +360,7 @@ private extension SwiftPackageFileAnalyzer { listOfChanges += removedProductDependencies.map { "Removed dependency .product(name: \"\($0)\", ...)" } return [.init( - changeType: .change( + changeType: .modification( oldDescription: oldTarget.description, newDescription: newTarget.description ), @@ -425,7 +425,7 @@ private extension SwiftPackageFileAnalyzer { guard oldDependency != newDependency else { return [] } return [.init( - changeType: .change( + changeType: .modification( oldDescription: oldDependency.description, newDescription: newDependency.description ), @@ -443,7 +443,7 @@ private extension SwiftPackageFileAnalyzer { guard old != new else { return [] } return [.init( - changeType: .change( + changeType: .modification( oldDescription: "// swift-tools-version: \(old)", newDescription: "// swift-tools-version: \(new)" ), diff --git a/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceAnalyzer/SwiftInterfaceChangeConsolidator.swift b/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceAnalyzer/SwiftInterfaceChangeConsolidator.swift index 439fab7..f76e802 100644 --- a/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceAnalyzer/SwiftInterfaceChangeConsolidator.swift +++ b/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceAnalyzer/SwiftInterfaceChangeConsolidator.swift @@ -58,7 +58,7 @@ struct SwiftInterfaceChangeConsolidator: SwiftInterfaceChangeConsolidating { consolidatedChanges.append( .init( - changeType: .change( + changeType: .modification( oldDescription: oldDescription, newDescription: newDescription ), diff --git a/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceParser/SwiftInterfaceElement/SwiftInterfaceElement+DiffHelper.swift b/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceParser/SwiftInterfaceElement/SwiftInterfaceElement+DiffHelper.swift index 937ceac..88de350 100644 --- a/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceParser/SwiftInterfaceElement/SwiftInterfaceElement+DiffHelper.swift +++ b/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceParser/SwiftInterfaceElement/SwiftInterfaceElement+DiffHelper.swift @@ -31,7 +31,7 @@ extension SwiftInterfaceElement { } switch changeType { - case let .change(old, new): + case let .modification(old, new): diffDescription += " from `\(old)` to `\(new)`" case let .removal(string): diffDescription += " `\(string)`" @@ -74,13 +74,13 @@ extension SwiftInterfaceElement { /// File-private helper to produce detailed descriptions private enum ChangeType { - case change(old: String, new: String) + case modification(old: String, new: String) case removal(String) case addition(String) var title: String { switch self { - case .change: "Changed" + case .modification: "Modified" case .removal: "Removed" case .addition: "Added" } @@ -88,7 +88,7 @@ private enum ChangeType { static func `for`(oldValue: String?, newValue: String?) -> Self? { if oldValue == newValue { return nil } - if let oldValue, let newValue { return .change(old: oldValue, new: newValue) } + if let oldValue, let newValue { return .modification(old: oldValue, new: newValue) } if let oldValue { return .removal(oldValue) } if let newValue { return .addition(newValue) } return nil diff --git a/Sources/Shared/Public/PADCore/Change.swift b/Sources/Shared/Public/PADCore/Change.swift index 0d18af1..ebe4e1a 100644 --- a/Sources/Shared/Public/PADCore/Change.swift +++ b/Sources/Shared/Public/PADCore/Change.swift @@ -11,7 +11,7 @@ public struct Change: Equatable { public enum ChangeType: Equatable { case addition(description: String) case removal(description: String) - case change(oldDescription: String, newDescription: String) + case modification(oldDescription: String, newDescription: String) } public private(set) var changeType: ChangeType @@ -38,7 +38,7 @@ extension Change.ChangeType { return true case .removal: return false - case .change: + case .modification: return false } } @@ -49,18 +49,18 @@ extension Change.ChangeType { return false case .removal: return true - case .change: + case .modification: return false } } - public var isChange: Bool { + public var isModification: Bool { switch self { case .addition: return false case .removal: return false - case .change: + case .modification: return true } } diff --git a/Tests/IntegrationTests/Resources/expected-reference-changes-swift-interface-private.md b/Tests/IntegrationTests/Resources/expected-reference-changes-swift-interface-private.md index e51cecc..7fc5952 100644 --- a/Tests/IntegrationTests/Resources/expected-reference-changes-swift-interface-private.md +++ b/Tests/IntegrationTests/Resources/expected-reference-changes-swift-interface-private.md @@ -1,5 +1,6 @@ -# πŸ‘€ 54 public changes detected +# ⚠️ 54 public changes detected ⚠️ _Comparing `new_private` to `old_private`_ +
❇️31 Additions
πŸ”€21 Modifications
❌2 Removals
--- ## `ReferencePackage` @@ -32,7 +33,7 @@ public protocol ParentProtocol { public protocol SimpleProtocol ``` -#### πŸ”€ Changed +#### πŸ”€ Modified ```javascript // From @_spi(SystemProgrammingInterface) @@ -135,7 +136,7 @@ public subscript(index: Swift.Int) -> T? { get set } public var lazyVar: Swift.String { get set } ``` -#### πŸ”€ Changed +#### πŸ”€ Modified ```javascript // From @_Concurrency.MainActor @@ -215,7 +216,7 @@ public struct NestedStructInExtension { } ``` -#### πŸ”€ Changed +#### πŸ”€ Modified ```javascript // From case caseWithTuple( @@ -250,7 +251,7 @@ Changes: - Removed parameter `ReferencePackage.CustomEnum` */ ``` -#### πŸ˜Άβ€πŸŒ«οΈ Removed +#### ❌ Removed ```javascript case caseWithString(Swift.String) ``` @@ -272,7 +273,7 @@ associatedtype CustomAssociatedType: Swift.Equatable associatedtype CustomAssociatedType: Swift.Equatable ``` -#### πŸ”€ Changed +#### πŸ”€ Modified ```javascript // From func function() -> any Swift.Equatable @@ -282,7 +283,7 @@ func function() -> Self.CustomAssociatedType /** Changes: -- Changed return type from `any Swift.Equatable` to `Self.CustomAssociatedType` +- Modified return type from `any Swift.Equatable` to `Self.CustomAssociatedType` */ ``` ```javascript @@ -294,7 +295,7 @@ var getSetVar: Self.AnotherAssociatedType { get set } /** Changes: -- Changed type from `any Swift.Equatable` to `Self.AnotherAssociatedType` +- Modified type from `any Swift.Equatable` to `Self.AnotherAssociatedType` */ ``` ```javascript @@ -306,10 +307,10 @@ var getVar: Self.CustomAssociatedType { get } /** Changes: -- Changed type from `any Swift.Equatable` to `Self.CustomAssociatedType` +- Modified type from `any Swift.Equatable` to `Self.CustomAssociatedType` */ ``` -#### πŸ˜Άβ€πŸŒ«οΈ Removed +#### ❌ Removed ```javascript typealias CustomAssociatedType = Swift.Equatable ``` @@ -341,7 +342,7 @@ public typealias Iterator = [ReferencePackage.CustomStruct.AnotherAssociatedT public typealias ParentType = Swift.Double ``` -#### πŸ”€ Changed +#### πŸ”€ Modified ```javascript // From @discardableResult @@ -353,7 +354,7 @@ public func function() -> Swift.Int /** Changes: -- Changed return type from `any Swift.Equatable` to `Swift.Int` +- Modified return type from `any Swift.Equatable` to `Swift.Int` */ ``` ```javascript @@ -365,7 +366,7 @@ public var getSetVar: Swift.Double /** Changes: -- Changed type from `any Swift.Equatable` to `Swift.Double` +- Modified type from `any Swift.Equatable` to `Swift.Double` */ ``` ```javascript @@ -377,7 +378,7 @@ public var getVar: Swift.Int /** Changes: -- Changed type from `any Swift.Equatable` to `Swift.Int` +- Modified type from `any Swift.Equatable` to `Swift.Int` */ ``` ### `OpenSpiConformingClass` @@ -397,7 +398,7 @@ public typealias Iterator = [Swift.Double] public typealias ParentType = Swift.Double ``` -#### πŸ”€ Changed +#### πŸ”€ Modified ```javascript // From @_spi(SystemProgrammingInterface) @@ -411,7 +412,7 @@ public func function() -> T /** Changes: -- Changed return type from `ReferencePackage.OpenSpiConformingClass.CustomAssociatedType` to `T` +- Modified return type from `ReferencePackage.OpenSpiConformingClass.CustomAssociatedType` to `T` */ ``` ```javascript @@ -448,7 +449,7 @@ public typealias CustomAssociatedType = T /** Changes: -- Changed assignment from `any Swift.Equatable` to `T` +- Modified assignment from `any Swift.Equatable` to `T` */ ``` ```javascript @@ -462,7 +463,7 @@ public var getSetVar: T /** Changes: -- Changed type from `ReferencePackage.OpenSpiConformingClass.CustomAssociatedType` to `T` +- Modified type from `ReferencePackage.OpenSpiConformingClass.CustomAssociatedType` to `T` */ ``` ```javascript @@ -476,7 +477,7 @@ public var getVar: T /** Changes: -- Changed type from `ReferencePackage.OpenSpiConformingClass.CustomAssociatedType` to `T` +- Modified type from `ReferencePackage.OpenSpiConformingClass.CustomAssociatedType` to `T` */ ``` diff --git a/Tests/IntegrationTests/Resources/expected-reference-changes-swift-interface-public.md b/Tests/IntegrationTests/Resources/expected-reference-changes-swift-interface-public.md index 49ac36f..524fbba 100644 --- a/Tests/IntegrationTests/Resources/expected-reference-changes-swift-interface-public.md +++ b/Tests/IntegrationTests/Resources/expected-reference-changes-swift-interface-public.md @@ -1,5 +1,6 @@ -# πŸ‘€ 45 public changes detected +# ⚠️ 45 public changes detected ⚠️ _Comparing `new_public` to `old_public`_ +
❇️28 Additions
πŸ”€15 Modifications
❌2 Removals
--- ## `ReferencePackage` @@ -32,7 +33,7 @@ public protocol ParentProtocol { public protocol SimpleProtocol ``` -#### πŸ”€ Changed +#### πŸ”€ Modified ```javascript // From public actor CustomActor @@ -120,7 +121,7 @@ public subscript(index: Swift.Int) -> T? { get set } public var lazyVar: Swift.String { get set } ``` -#### πŸ”€ Changed +#### πŸ”€ Modified ```javascript // From @_Concurrency.MainActor @@ -200,7 +201,7 @@ public struct NestedStructInExtension { } ``` -#### πŸ”€ Changed +#### πŸ”€ Modified ```javascript // From case caseWithTuple( @@ -235,7 +236,7 @@ Changes: - Removed parameter `ReferencePackage.CustomEnum` */ ``` -#### πŸ˜Άβ€πŸŒ«οΈ Removed +#### ❌ Removed ```javascript case caseWithString(Swift.String) ``` @@ -257,7 +258,7 @@ associatedtype CustomAssociatedType: Swift.Equatable associatedtype CustomAssociatedType: Swift.Equatable ``` -#### πŸ”€ Changed +#### πŸ”€ Modified ```javascript // From func function() -> any Swift.Equatable @@ -267,7 +268,7 @@ func function() -> Self.CustomAssociatedType /** Changes: -- Changed return type from `any Swift.Equatable` to `Self.CustomAssociatedType` +- Modified return type from `any Swift.Equatable` to `Self.CustomAssociatedType` */ ``` ```javascript @@ -279,7 +280,7 @@ var getSetVar: Self.AnotherAssociatedType { get set } /** Changes: -- Changed type from `any Swift.Equatable` to `Self.AnotherAssociatedType` +- Modified type from `any Swift.Equatable` to `Self.AnotherAssociatedType` */ ``` ```javascript @@ -291,10 +292,10 @@ var getVar: Self.CustomAssociatedType { get } /** Changes: -- Changed type from `any Swift.Equatable` to `Self.CustomAssociatedType` +- Modified type from `any Swift.Equatable` to `Self.CustomAssociatedType` */ ``` -#### πŸ˜Άβ€πŸŒ«οΈ Removed +#### ❌ Removed ```javascript typealias CustomAssociatedType = Swift.Equatable ``` @@ -326,7 +327,7 @@ public typealias Iterator = [ReferencePackage.CustomStruct.AnotherAssociatedT public typealias ParentType = Swift.Double ``` -#### πŸ”€ Changed +#### πŸ”€ Modified ```javascript // From @discardableResult @@ -338,7 +339,7 @@ public func function() -> Swift.Int /** Changes: -- Changed return type from `any Swift.Equatable` to `Swift.Int` +- Modified return type from `any Swift.Equatable` to `Swift.Int` */ ``` ```javascript @@ -350,7 +351,7 @@ public var getSetVar: Swift.Double /** Changes: -- Changed type from `any Swift.Equatable` to `Swift.Double` +- Modified type from `any Swift.Equatable` to `Swift.Double` */ ``` ```javascript @@ -362,7 +363,7 @@ public var getVar: Swift.Int /** Changes: -- Changed type from `any Swift.Equatable` to `Swift.Int` +- Modified type from `any Swift.Equatable` to `Swift.Int` */ ``` diff --git a/Tests/UnitTests/OutputGeneratorTests.swift b/Tests/UnitTests/OutputGeneratorTests.swift index 566a145..2163001 100644 --- a/Tests/UnitTests/OutputGeneratorTests.swift +++ b/Tests/UnitTests/OutputGeneratorTests.swift @@ -35,7 +35,8 @@ class OutputGeneratorTests: XCTestCase { let expectedOutput = """ # πŸ‘€ 1 public change detected _Comparing `new_source` to `old_source`_ - +
❇️1 Addition
+ --- ## `Target_1` #### ❇️ Added @@ -62,8 +63,9 @@ class OutputGeneratorTests: XCTestCase { func test_multipleChanges_multipleModules() { let expectedOutput = """ - # πŸ‘€ 4 public changes detected + # ⚠️ 4 public changes detected ⚠️ _Comparing `new_source` to `old_repository @ old_branch`_ +
❇️2 Additions
❌2 Removals
--- ## `Target_1` @@ -71,7 +73,7 @@ class OutputGeneratorTests: XCTestCase { ```javascript Some Addition ``` - #### πŸ˜Άβ€πŸŒ«οΈ Removed + #### ❌ Removed ```javascript Some Removal ``` @@ -80,7 +82,7 @@ class OutputGeneratorTests: XCTestCase { ```javascript Another Addition ``` - #### πŸ˜Άβ€πŸŒ«οΈ Removed + #### ❌ Removed ```javascript Another Removal ``` diff --git a/Tests/UnitTests/SwiftPackageFileAnalyzerTests.swift b/Tests/UnitTests/SwiftPackageFileAnalyzerTests.swift index 18d7877..0f4f519 100644 --- a/Tests/UnitTests/SwiftPackageFileAnalyzerTests.swift +++ b/Tests/UnitTests/SwiftPackageFileAnalyzerTests.swift @@ -130,7 +130,7 @@ class SwiftPackageFileAnalyzerTests: XCTestCase { let expectedChanges: [Change] = [ .init( - changeType: .change( + changeType: .modification( oldDescription: "// swift-tools-version: 2.0", newDescription: "// swift-tools-version: 1.0" ), @@ -138,7 +138,7 @@ class SwiftPackageFileAnalyzerTests: XCTestCase { listOfChanges: [] ), .init( - changeType: .change( + changeType: .modification( oldDescription: "defaultLocalization: \"nl-nl\"", newDescription: "defaultLocalization: \"en-us\"" ), @@ -146,7 +146,7 @@ class SwiftPackageFileAnalyzerTests: XCTestCase { listOfChanges: [] ), .init( - changeType: .change( + changeType: .modification( oldDescription: "name: \"Old Name\"", newDescription: "name: \"New Name\"" ), @@ -154,7 +154,7 @@ class SwiftPackageFileAnalyzerTests: XCTestCase { listOfChanges: [] ), .init( - changeType: .change( + changeType: .modification( oldDescription: "platforms: [.iOS(12.0), .macOS(10.0)]", newDescription: "platforms: [.iOS(15.0), .visionOS(1.0)]" ), @@ -173,7 +173,7 @@ class SwiftPackageFileAnalyzerTests: XCTestCase { listOfChanges: [] ), .init( - changeType: .change( + changeType: .modification( oldDescription: ".library(name: \"Some Library\", targets: [\"Some Target\", \"Old Target\"])", newDescription: ".library(name: \"Some Library\", targets: [\"Some Target\", \"New Target\"])" ), @@ -198,7 +198,7 @@ class SwiftPackageFileAnalyzerTests: XCTestCase { listOfChanges: [] ), .init( - changeType: .change( + changeType: .modification( oldDescription: ".binaryTarget(name: \"Some Target\", dependencies: [.target(name: \"Some Target Dependency\"), .target(name: \"Old Target Dependency\"), .product(name: \"Some Product Dependency\", ...), .product(name: \"Old Product Dependency\", ...)], path: \"some/old/path\")", newDescription: ".target(name: \"Some Target\", dependencies: [.target(name: \"Some Target Dependency\"), .target(name: \"New Target Dependency\"), .product(name: \"Some Product Dependency\", ...), .product(name: \"New Product Dependency\", ...)], path: \"some/new/path\")" ),