diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 2d0a3e4..bf2cacd 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -21,7 +21,7 @@ jobs: - name: Select latest Xcode uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: '16.1' + xcode-version: '16.2' - name: 🛠️ Run All Tests run: | diff --git a/Package.resolved b/Package.resolved index 28f7ba1..ab0f8b8 100644 --- a/Package.resolved +++ b/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-argument-parser", "state" : { - "revision" : "41982a3656a71c768319979febd796c6fd111d5c", - "version" : "1.5.0" + "revision" : "011f0c765fb46d9cac61bca19be0527e99c98c8b", + "version" : "1.5.1" } }, { @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-docc-plugin", "state" : { - "revision" : "85e4bb4e1cd62cec64a4b8e769dcefdf0c5b9d64", - "version" : "1.4.3" + "revision" : "d1691545d53581400b1de9b0472d45eb25c19fed", + "version" : "1.4.4" } }, { @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/nicklockwood/SwiftFormat", "state" : { - "revision" : "468a7d32dedc8d352c191594b3b45d9fd8ba291b", - "version" : "0.55.5" + "revision" : "6ebb96ce454ddb036320104a1160350ee9581767", + "version" : "0.56.4" } }, { diff --git a/ReferencePackages/ReferencePackage/Sources/ReferencePackage/ReferencePackage.swift b/ReferencePackages/ReferencePackage/Sources/ReferencePackage/ReferencePackage.swift index 86f3687..7713fa4 100644 --- a/ReferencePackages/ReferencePackage/Sources/ReferencePackage/ReferencePackage.swift +++ b/ReferencePackages/ReferencePackage/Sources/ReferencePackage/ReferencePackage.swift @@ -140,3 +140,20 @@ public enum CustomEnum { indirect case recursive(CustomEnum) } + +public struct PublicStructThatIsOnlyAvailableInTheReferencePackage { + + public var foo: String + public func bar() -> Void { + print("Hello") + } +} + +public extension CustomEnum { + enum PublicEnumInExtensionOfCustomEnumThatIsOnlyAvailableInTheReferencePackage { + case alpha + case beta + } +} + +public extension CustomEnum {} diff --git a/Sources/PublicModules/PADProjectBuilder/SwiftInterfaceProducer/XcodeTools.swift b/Sources/PublicModules/PADProjectBuilder/SwiftInterfaceProducer/XcodeTools.swift index e2405e3..177d617 100644 --- a/Sources/PublicModules/PADProjectBuilder/SwiftInterfaceProducer/XcodeTools.swift +++ b/Sources/PublicModules/PADProjectBuilder/SwiftInterfaceProducer/XcodeTools.swift @@ -23,6 +23,7 @@ struct XcodeTools { internal enum Constants { static let derivedDataPath: String = ".build" + static let buildDirPath: String = ".build/Build" static let simulatorSdkCommand = "xcrun --sdk iphonesimulator --show-sdk-path" } @@ -87,6 +88,7 @@ struct XcodeTools { let result = shell.execute(command) let derivedDataPath = "\(projectDirectoryPath)/\(Constants.derivedDataPath)" + let buildDirPath = "\(projectDirectoryPath)/\(Constants.buildDirPath)" logger?.debug(result, from: String(describing: Self.self)) @@ -94,9 +96,7 @@ struct XcodeTools { // so we have to check outside if they exist. // // Also see: https://github.com/swiftlang/swift/issues/56573 - guard fileHandler.fileExists(atPath: derivedDataPath) else { - print(result) - + guard fileHandler.fileExists(atPath: buildDirPath) else { throw XcodeToolsError( errorDescription: "💥 Building project failed", underlyingError: result diff --git a/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceAnalyzer/SwiftInterfaceAnalyzer.swift b/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceAnalyzer/SwiftInterfaceAnalyzer.swift index 8cf9481..762b0a7 100644 --- a/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceAnalyzer/SwiftInterfaceAnalyzer.swift +++ b/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceAnalyzer/SwiftInterfaceAnalyzer.swift @@ -81,7 +81,7 @@ struct SwiftInterfaceAnalyzer: SwiftInterfaceAnalyzing { // No matching element was found so either it was removed or added let changeType: IndependentSwiftInterfaceChange.ChangeType = oldFirst ? - .removal(lhsElement.description) : + .removal(lhsElement.recursiveDescription()) : .addition(lhsElement.recursiveDescription()) return [ diff --git a/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceParser/DeclSyntax+SwiftInterface/EnumCaseDeclSyntax+SwiftInterface.swift b/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceParser/DeclSyntax+SwiftInterface/EnumCaseDeclSyntax+SwiftInterface.swift index aff5b78..df57620 100644 --- a/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceParser/DeclSyntax+SwiftInterface/EnumCaseDeclSyntax+SwiftInterface.swift +++ b/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceParser/DeclSyntax+SwiftInterface/EnumCaseDeclSyntax+SwiftInterface.swift @@ -22,6 +22,7 @@ extension EnumCaseDeclSyntax { name: $0.name.trimmedDescription, parameters: $0.parameterClause?.parameters.map { .init( + attributes: [], firstName: $0.firstName?.trimmedDescription, secondName: $0.secondName?.trimmedDescription, type: $0.type.trimmedDescription, diff --git a/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceParser/DeclSyntax+SwiftInterface/FunctionDeclSyntax+SwiftInterface.swift b/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceParser/DeclSyntax+SwiftInterface/FunctionDeclSyntax+SwiftInterface.swift index a9f8da7..c5f200c 100644 --- a/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceParser/DeclSyntax+SwiftInterface/FunctionDeclSyntax+SwiftInterface.swift +++ b/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceParser/DeclSyntax+SwiftInterface/FunctionDeclSyntax+SwiftInterface.swift @@ -23,7 +23,7 @@ extension FunctionDeclSyntax { } } - let parameters: [SwiftInterfaceFunction.Parameter] = self.signature.parameterClause.parameters.map { + let parameters: [SwiftInterfaceElementParameter] = self.signature.parameterClause.parameters.map { .init( attributes: $0.attributes.sanitizedList, firstName: $0.firstName.trimmedDescription, diff --git a/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceParser/DeclSyntax+SwiftInterface/InitializerDeclSyntax+SwiftInterface.swift b/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceParser/DeclSyntax+SwiftInterface/InitializerDeclSyntax+SwiftInterface.swift index 47afcd2..1d5360b 100644 --- a/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceParser/DeclSyntax+SwiftInterface/InitializerDeclSyntax+SwiftInterface.swift +++ b/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceParser/DeclSyntax+SwiftInterface/InitializerDeclSyntax+SwiftInterface.swift @@ -23,7 +23,7 @@ extension InitializerDeclSyntax { } } - let parameters: [SwiftInterfaceFunction.Parameter] = self.signature.parameterClause.parameters.map { + let parameters: [SwiftInterfaceElementParameter] = self.signature.parameterClause.parameters.map { .init( attributes: $0.attributes.sanitizedList, firstName: $0.firstName.trimmedDescription, diff --git a/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceParser/DeclSyntax+SwiftInterface/SubscriptDeclSyntax+SwiftInterface.swift b/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceParser/DeclSyntax+SwiftInterface/SubscriptDeclSyntax+SwiftInterface.swift index 8c766f2..595ba95 100644 --- a/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceParser/DeclSyntax+SwiftInterface/SubscriptDeclSyntax+SwiftInterface.swift +++ b/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceParser/DeclSyntax+SwiftInterface/SubscriptDeclSyntax+SwiftInterface.swift @@ -12,8 +12,9 @@ extension SubscriptDeclSyntax { func toInterfaceElement() -> SwiftInterfaceSubscript { - let parameters: [SwiftInterfaceSubscript.Parameter] = self.parameterClause.parameters.map { + let parameters: [SwiftInterfaceElementParameter] = self.parameterClause.parameters.map { .init( + attributes: $0.attributes.sanitizedList, firstName: $0.firstName.trimmedDescription, secondName: $0.secondName?.trimmedDescription, type: $0.type.trimmedDescription, diff --git a/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceParser/SwiftInterfaceElement+Declaration/SwiftInterfaceElement+EnumCase.swift b/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceParser/SwiftInterfaceElement+Declaration/SwiftInterfaceElement+EnumCase.swift index e14a055..fe5cd40 100644 --- a/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceParser/SwiftInterfaceElement+Declaration/SwiftInterfaceElement+EnumCase.swift +++ b/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceParser/SwiftInterfaceElement+Declaration/SwiftInterfaceElement+EnumCase.swift @@ -6,39 +6,6 @@ import Foundation -extension SwiftInterfaceEnumCase { - - struct Parameter { - - let firstName: String? - - let secondName: String? - - let type: String - - let defaultValue: String? - - var description: String { - var description = [ - firstName, - secondName - ].compactMap { $0 }.joined(separator: " ") - - if description.isEmpty { - description += "\(type)" - } else { - description += ": \(type)" - } - - if let defaultValue { - description += " = \(defaultValue)" - } - - return description - } - } -} - class SwiftInterfaceEnumCase: SwiftInterfaceElement { /// e.g. @discardableResult, @MainActor, @objc, @_spi(...), ... @@ -49,7 +16,7 @@ class SwiftInterfaceEnumCase: SwiftInterfaceElement { let name: String - let parameters: [Parameter]? + let parameters: [SwiftInterfaceElementParameter]? let rawValue: String? @@ -72,7 +39,7 @@ class SwiftInterfaceEnumCase: SwiftInterfaceElement { attributes: [String], modifiers: [String], name: String, - parameters: [Parameter]?, + parameters: [SwiftInterfaceElementParameter]?, rawValue: String? ) { self.attributes = attributes @@ -90,7 +57,7 @@ extension SwiftInterfaceEnumCase { guard let other = otherElement as? Self else { return [] } changes += diffDescription(propertyType: "attribute", oldValues: other.attributes, newValues: attributes) changes += diffDescription(propertyType: "modifier", oldValues: other.modifiers, newValues: modifiers) - changes += diffDescription(propertyType: "parameter", oldValues: other.parameters?.map(\.description), newValues: parameters?.map(\.description)) + changes += diffDescription(oldParameters: other.parameters, newParameters: parameters) changes += diffDescription(propertyType: "raw value", oldValue: other.rawValue, newValue: rawValue) return changes.compactMap { $0 } } diff --git a/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceParser/SwiftInterfaceElement+Declaration/SwiftInterfaceElement+Function.swift b/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceParser/SwiftInterfaceElement+Declaration/SwiftInterfaceElement+Function.swift index 9d937d3..89f0aaa 100644 --- a/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceParser/SwiftInterfaceElement+Declaration/SwiftInterfaceElement+Function.swift +++ b/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceParser/SwiftInterfaceElement+Declaration/SwiftInterfaceElement+Function.swift @@ -6,45 +6,6 @@ import Foundation -extension SwiftInterfaceFunction { - - struct Parameter { - - /// e.g. @discardableResult, @MainActor, @objc, @_spi(...), ... - let attributes: [String] - - let firstName: String - - /// optional second "internal" name - can be ignored - let secondName: String? - - let type: String - - let defaultValue: String? - - var description: String { - let names = [ - firstName, - secondName - ].compactMap { $0 } - - var description = (attributes + names).joined(separator: " ") - - if description.isEmpty { - description += "\(type)" - } else { - description += ": \(type)" - } - - if let defaultValue { - description += " = \(defaultValue)" - } - - return description - } - } -} - class SwiftInterfaceFunction: SwiftInterfaceElement { /// e.g. @discardableResult, @MainActor, @objc, @_spi(...), ... @@ -55,7 +16,7 @@ class SwiftInterfaceFunction: SwiftInterfaceElement { /// e.g. let genericParameterDescription: String? - let parameters: [Parameter] + let parameters: [SwiftInterfaceElementParameter] /// e.g. async, throws, rethrows let effectSpecifiers: [String] @@ -76,7 +37,7 @@ class SwiftInterfaceFunction: SwiftInterfaceElement { var parent: (any SwiftInterfaceElement)? var diffableSignature: String { - "\(name)(\(parameters.map { "\($0.firstName):" }.joined()))" + "\(name)(\(parameters.map(\.valueForDiffableSignature).joined()))" } var consolidatableName: String { name } @@ -90,7 +51,7 @@ class SwiftInterfaceFunction: SwiftInterfaceElement { modifiers: [String], name: String, genericParameterDescription: String?, - parameters: [Parameter], + parameters: [SwiftInterfaceElementParameter], effectSpecifiers: [String], returnType: String?, genericWhereClauseDescription: String? @@ -114,7 +75,7 @@ extension SwiftInterfaceFunction { changes += diffDescription(propertyType: "attribute", oldValues: other.attributes, newValues: attributes) changes += diffDescription(propertyType: "modifier", oldValues: other.modifiers, newValues: modifiers) changes += diffDescription(propertyType: "generic parameter description", oldValue: other.genericParameterDescription, newValue: genericParameterDescription) - changes += diffDescription(propertyType: "parameter", oldValues: other.parameters.map(\.description), newValues: parameters.map(\.description)) // TODO: Maybe have a better way to show changes + changes += diffDescription(oldParameters: other.parameters, newParameters: parameters) changes += diffDescription(propertyType: "effect", oldValues: other.effectSpecifiers, newValues: effectSpecifiers) changes += diffDescription(propertyType: "return type", oldValue: other.returnType, newValue: returnType) changes += diffDescription(propertyType: "generic where clause", oldValue: other.genericWhereClauseDescription, newValue: genericWhereClauseDescription) diff --git a/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceParser/SwiftInterfaceElement+Declaration/SwiftInterfaceElement+Initializer.swift b/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceParser/SwiftInterfaceElement+Declaration/SwiftInterfaceElement+Initializer.swift index fafca00..6aa6de3 100644 --- a/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceParser/SwiftInterfaceElement+Declaration/SwiftInterfaceElement+Initializer.swift +++ b/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceParser/SwiftInterfaceElement+Declaration/SwiftInterfaceElement+Initializer.swift @@ -16,7 +16,7 @@ class SwiftInterfaceInitializer: SwiftInterfaceElement { /// e.g. let genericParameterDescription: String? - let parameters: [SwiftInterfaceFunction.Parameter] + let parameters: [SwiftInterfaceElementParameter] /// e.g. async, throws, rethrows let effectSpecifiers: [String] @@ -35,7 +35,7 @@ class SwiftInterfaceInitializer: SwiftInterfaceElement { var parent: (any SwiftInterfaceElement)? var diffableSignature: String { - "init(\(parameters.map { "\($0.firstName):" }.joined()))" + "init(\(parameters.map { $0.valueForDiffableSignature }.joined()))" } var consolidatableName: String { "init" } @@ -49,7 +49,7 @@ class SwiftInterfaceInitializer: SwiftInterfaceElement { modifiers: [String], optionalMark: String?, genericParameterDescription: String?, - parameters: [SwiftInterfaceFunction.Parameter], + parameters: [SwiftInterfaceElementParameter], effectSpecifiers: [String], genericWhereClauseDescription: String? ) { @@ -72,7 +72,7 @@ extension SwiftInterfaceInitializer { changes += diffDescription(propertyType: "modifier", oldValues: other.modifiers, newValues: modifiers) changes += diffDescription(propertyType: "optional mark", oldValue: other.optionalMark, newValue: optionalMark) changes += diffDescription(propertyType: "generic parameter description", oldValue: other.genericParameterDescription, newValue: genericParameterDescription) - changes += diffDescription(propertyType: "parameter", oldValues: other.parameters.map(\.description), newValues: parameters.map(\.description)) // TODO: Maybe have a better way to show changes + changes += diffDescription(oldParameters: other.parameters, newParameters: parameters) changes += diffDescription(propertyType: "effect", oldValues: other.effectSpecifiers, newValues: effectSpecifiers) changes += diffDescription(propertyType: "generic where clause", oldValue: other.genericWhereClauseDescription, newValue: genericWhereClauseDescription) return changes.compactMap { $0 } diff --git a/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceParser/SwiftInterfaceElement+Declaration/SwiftInterfaceElement+Subscript.swift b/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceParser/SwiftInterfaceElement+Declaration/SwiftInterfaceElement+Subscript.swift index 676aabe..7c295fd 100644 --- a/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceParser/SwiftInterfaceElement+Declaration/SwiftInterfaceElement+Subscript.swift +++ b/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceParser/SwiftInterfaceElement+Declaration/SwiftInterfaceElement+Subscript.swift @@ -6,40 +6,6 @@ import Foundation -extension SwiftInterfaceSubscript { - - struct Parameter { - - let firstName: String - - /// optional second "internal" name - can be ignored - let secondName: String? - - let type: String - - let defaultValue: String? - - var description: String { - var description = [ - firstName, - secondName - ].compactMap { $0 }.joined(separator: " ") - - if description.isEmpty { - description += "\(type)" - } else { - description += ": \(type)" - } - - if let defaultValue { - description += " = \(defaultValue)" - } - - return description - } - } -} - class SwiftInterfaceSubscript: SwiftInterfaceElement { let name: String = "subscript" @@ -53,7 +19,7 @@ class SwiftInterfaceSubscript: SwiftInterfaceElement { /// e.g. let genericParameterDescription: String? - let parameters: [Parameter] + let parameters: [SwiftInterfaceElementParameter] let returnType: String @@ -69,7 +35,7 @@ class SwiftInterfaceSubscript: SwiftInterfaceElement { var parent: (any SwiftInterfaceElement)? var diffableSignature: String { - "\(name)(\(parameters.map { "\($0.firstName):" }.joined()))" + "\(name)(\(parameters.map(\.valueForDiffableSignature).joined()))" } var consolidatableName: String { name } @@ -82,7 +48,7 @@ class SwiftInterfaceSubscript: SwiftInterfaceElement { attributes: [String], modifiers: [String], genericParameterDescription: String?, - parameters: [Parameter], + parameters: [SwiftInterfaceElementParameter], returnType: String, genericWhereClauseDescription: String?, accessors: String? @@ -105,7 +71,7 @@ extension SwiftInterfaceSubscript { changes += diffDescription(propertyType: "attribute", oldValues: other.attributes, newValues: attributes) changes += diffDescription(propertyType: "modifier", oldValues: other.modifiers, newValues: modifiers) changes += diffDescription(propertyType: "generic parameter description", oldValue: other.genericParameterDescription, newValue: genericParameterDescription) - changes += diffDescription(propertyType: "parameter", oldValues: other.parameters.map(\.description), newValues: parameters.map(\.description)) // TODO: Maybe have a better way to show changes + changes += diffDescription(oldParameters: other.parameters, newParameters: parameters) changes += diffDescription(propertyType: "return type", oldValue: other.returnType, newValue: returnType) changes += diffDescription(propertyType: "generic where clause", oldValue: other.genericWhereClauseDescription, newValue: genericWhereClauseDescription) changes += diffDescription(propertyType: "accessors", oldValue: other.accessors, newValue: accessors) diff --git a/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceParser/SwiftInterfaceElement/SwiftInterfaceElement+DiffHelper+Parameter.swift b/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceParser/SwiftInterfaceElement/SwiftInterfaceElement+DiffHelper+Parameter.swift new file mode 100644 index 0000000..f1f65a9 --- /dev/null +++ b/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceParser/SwiftInterfaceElement/SwiftInterfaceElement+DiffHelper+Parameter.swift @@ -0,0 +1,116 @@ +// +// SwiftInterfaceElement+DiffHelper+Parameter.swift +// public-api-diff +// +// Created by Alexander Guretzki on 30/04/2025. +// + +import Foundation +import PADCore + +extension SwiftInterfaceElement { + + /// Returns a list of change descriptions for changes between the old and new values + /// - Parameters: + /// - propertyType: The property type name (e.g. "accessor", "modifier", "generic where clause", ...) + /// for additional information + /// - oldParameters: The (optional) old parameters + /// - newParameters: The (optional) new parameters + /// - Returns: A list of change descriptions caused by a value change + func diffDescription( + oldParameters: [SwiftInterfaceElementParameter]?, + newParameters: [SwiftInterfaceElementParameter]? + ) -> [String] { + let propertyType = "parameter" + + guard let oldParameters else { + guard let newParameters else { return [] } + return newParameters.map { "Added \(propertyType) `\($0.description)`" } + } + + guard let newParameters else { + return oldParameters.map { "Removed \(propertyType) `\($0.description)`" } + } + + let oldParametersByName = oldParameters.indexedByFirstName + let newParametersByName = newParameters.indexedByFirstName + + var changes = [String]() + + // Check for removed parameters + oldParameters.enumerated().forEach { index, oldParameter in + let oldFirstName = oldParameter.firstName ?? "Parameter \(index)" + if newParametersByName[oldFirstName] == nil { + changes.append("Removed \(propertyType) `\(oldParameter.description)`") + } + } + + // Check for added and modified parameters + newParameters.enumerated().forEach { index, newParameter in + + let newFirstName = newParameter.firstName ?? "Parameter \(index)" + + guard let oldParameter = oldParametersByName[newFirstName] else { + // Parameter was added + changes.append("Added \(propertyType) `\(newParameter.description)`") + return + } + + let modificationDiffPrefix = modificationDiffDescriptionPrefix( + propertyType: propertyType, + firstName: oldParameter.firstName, + index: index + ) + + // Check if the type has changed + if oldParameter.type != newParameter.type { + changes.append("\(modificationDiffPrefix): Changed type from `\(oldParameter.type)` to `\(newParameter.type)`") + } + + // Check if the default value has changed + changes += diffDescription( + propertyType: "default value", + oldValue: oldParameter.defaultValue, + newValue: newParameter.defaultValue + ).map { "\(modificationDiffPrefix)`: \($0)" } + + // Check if the attributes did change + changes += diffDescription( + propertyType: "attribute", + oldValues: oldParameter.attributes, + newValues: newParameter.attributes + ).map { "\(modificationDiffPrefix)`: \($0)" } + } + + return changes + } +} + +fileprivate extension SwiftInterfaceElement { + + func modificationDiffDescriptionPrefix( + propertyType: String, + firstName: String?, + index: Int + ) -> String { + let ordinalFormatter = NumberFormatter() + ordinalFormatter.numberStyle = .ordinal + + if let firstName { + return "Modified \(propertyType) `\(firstName)`" + } else { + return "Modified \(ordinalFormatter.string(from: NSNumber(value: index+1)) ?? "\(index + 1)") \(propertyType)" + } + } +} + +private extension [SwiftInterfaceElementParameter] { + + var indexedByFirstName: [String: SwiftInterfaceElementParameter] { + Dictionary( + uniqueKeysWithValues: self.enumerated().map { + ($0.element.firstName ?? "Parameter \($0.offset)", $0.element) + } + ) + } +} diff --git a/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceParser/SwiftInterfaceElement/SwiftInterfaceElement+DiffHelper.swift b/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceParser/SwiftInterfaceElement/SwiftInterfaceElement+DiffHelper.swift index 88de350..71d2f8e 100644 --- a/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceParser/SwiftInterfaceElement/SwiftInterfaceElement+DiffHelper.swift +++ b/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceParser/SwiftInterfaceElement/SwiftInterfaceElement+DiffHelper.swift @@ -44,29 +44,32 @@ extension SwiftInterfaceElement { /// Returns a list of change descriptions for changes between the old and new values /// - Parameters: - /// - propertyType: The (optional) property type name (e.g. "accessor", "modifier", "generic where clause", ...) for additional information - /// - oldValue: The (optional) old values - /// - newValue: The (optional) new values + /// - propertyType: The property type name (e.g. "accessor", "modifier", "generic where clause", ...) + /// for additional information + /// - oldValues: The (optional) old values + /// - newValues: The (optional) new values /// - Returns: A list of change descriptions caused by a value change - func diffDescription(propertyType: String, oldValues: [String]?, newValues: [String]?) -> [String] { + func diffDescription( + propertyType: String, + oldValues: [String]?, + newValues: [String]? + ) -> [String] { - if let oldValues, let newValues { - let old = Set(oldValues) - let new = Set(newValues) - return old.symmetricDifference(new).map { - "\(new.contains($0) ? "Added" : "Removed") \(propertyType) `\($0)`" - } + guard let oldValues else { + guard let newValues else { return [] } + return newValues.map { "Added \(propertyType) `\($0.description)`" } } - if let oldValues { - return oldValues.map { "Removed \(propertyType) `\($0)`" } + guard let newValues else { + return oldValues.map { "Removed \(propertyType) `\($0.description)`" } } - - if let newValues { - return newValues.map { "Added \(propertyType) `\($0)`" } + + let old = Set(oldValues) + let new = Set(newValues) + + return old.symmetricDifference(new).map { + "\(new.contains($0) ? "Added" : "Removed") \(propertyType) `\($0)`" } - - return [] } } diff --git a/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceParser/SwiftInterfaceElement/SwiftInterfaceElement+ParameterDescription.swift b/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceParser/SwiftInterfaceElement/SwiftInterfaceElement+ParameterDescription.swift index 9875d9d..f99ace0 100644 --- a/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceParser/SwiftInterfaceElement/SwiftInterfaceElement+ParameterDescription.swift +++ b/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceParser/SwiftInterfaceElement/SwiftInterfaceElement+ParameterDescription.swift @@ -9,7 +9,7 @@ import Foundation extension SwiftInterfaceElement { func formattedParameterDescription(for parameterDescriptions: [String]) -> String { - // We're only doing multiline formatting if we have more than 1 character + // We're only doing multiline formatting if we have more than 1 parameter guard parameterDescriptions.count > 1 else { return parameterDescriptions.joined(separator: ", ") } let spacer = " " diff --git a/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceParser/SwiftInterfaceElement/SwiftInterfaceElementParameter.swift b/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceParser/SwiftInterfaceElement/SwiftInterfaceElementParameter.swift new file mode 100644 index 0000000..ba017f2 --- /dev/null +++ b/Sources/PublicModules/PADSwiftInterfaceDiff/SwiftInterfaceParser/SwiftInterfaceElement/SwiftInterfaceElementParameter.swift @@ -0,0 +1,50 @@ +// +// SwiftInterfaceElementParameter.swift +// public-api-diff +// +// Created by Alexander Guretzki on 30/04/2025. +// + + +struct SwiftInterfaceElementParameter { + + /// e.g. @discardableResult, @MainActor, @objc, @_spi(...), ... + let attributes: [String] + + let firstName: String? + + /// optional second "internal" name - can be ignored + let secondName: String? + + let type: String + + let defaultValue: String? + + var description: String { + let names = [ + firstName, + secondName + ].compactMap { $0 } + + var description = (attributes + names).joined(separator: " ") + + if description.isEmpty { + description += "\(type)" + } else { + description += ": \(type)" + } + + if let defaultValue { + description += " = \(defaultValue)" + } + + return description + } +} + +extension SwiftInterfaceElementParameter { + + var valueForDiffableSignature: String { + "\(firstName ?? "_"):" + } +} diff --git a/Tests/IntegrationTests/ReferencePackageTests.swift b/Tests/IntegrationTests/ReferencePackageTests.swift index 6287fff..08a825f 100644 --- a/Tests/IntegrationTests/ReferencePackageTests.swift +++ b/Tests/IntegrationTests/ReferencePackageTests.swift @@ -21,8 +21,8 @@ class ReferencePackageTests: XCTestCase { let newReferencePackageDirectory = referencePackagesRoot.appending(path: "UpdatedPackage") if - FileManager.default.fileExists(atPath: oldReferencePackageDirectory.appending(path: XcodeTools.Constants.derivedDataPath).path()), - FileManager.default.fileExists(atPath: newReferencePackageDirectory.appending(path: XcodeTools.Constants.derivedDataPath).path()) { + FileManager.default.fileExists(atPath: oldReferencePackageDirectory.appending(path: XcodeTools.Constants.buildDirPath).path()), + FileManager.default.fileExists(atPath: newReferencePackageDirectory.appending(path: XcodeTools.Constants.buildDirPath).path()) { return // Nothing to build } 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 d348fd2..8a105df 100644 --- a/Tests/IntegrationTests/Resources/expected-reference-changes-swift-interface-private.md +++ b/Tests/IntegrationTests/Resources/expected-reference-changes-swift-interface-private.md @@ -1,6 +1,6 @@ -# ⚠️ 58 public changes detected ⚠️ +# ⚠️ 60 public changes detected ⚠️ _Comparing `new_private` to `old_private`_ -
❇️34 Additions
🔀22 Modifications
2 Removals
+
❇️34 Additions
🔀22 Modifications
4 Removals
--- ## `ReferencePackage` @@ -121,6 +121,13 @@ Changes: - Added generic where clause `where T : Swift.Strideable` */ ``` +#### ❌ Removed +```swift +public struct PublicStructThatIsOnlyAvailableInTheReferencePackage { + public func bar() -> Swift.Void + public var foo: Swift.String +} +``` ### `Array` #### ❇️ Added ```swift @@ -271,14 +278,25 @@ indirect case recursive(ReferencePackage.CustomEnum) /** Changes: -- Added parameter `ReferencePackage.CustomEnum` -- Removed parameter `ReferencePackage.CustomEnum` +- Modified 1st parameter: Changed type from `ReferencePackage.CustomEnum` to `ReferencePackage.CustomEnum` */ ``` #### ❌ Removed ```swift case caseWithString(Swift.String) ``` +```swift +public enum PublicEnumInExtensionOfCustomEnumThatIsOnlyAvailableInTheReferencePackage: Swift.Equatable, Swift.Hashable { + case alpha + case beta + public func hash(into hasher: inout Swift.Hasher) -> Swift.Void + public static func ==( + a: ReferencePackage.CustomEnum.PublicEnumInExtensionOfCustomEnumThatIsOnlyAvailableInTheReferencePackage, + b: ReferencePackage.CustomEnum.PublicEnumInExtensionOfCustomEnumThatIsOnlyAvailableInTheReferencePackage + ) -> Swift.Bool + public var hashValue: Swift.Int { get } +} +``` ### `CustomProtocol` #### ❇️ Added ```swift @@ -444,10 +462,8 @@ public init( /** Changes: -- Added parameter `getSetVar: T` -- Added parameter `getVar: T` -- Removed parameter `getSetVar: ReferencePackage.OpenSpiConformingClass.CustomAssociatedType` -- Removed parameter `getVar: ReferencePackage.OpenSpiConformingClass.CustomAssociatedType` +- Modified parameter `getSetVar`: Changed type from `ReferencePackage.OpenSpiConformingClass.CustomAssociatedType` to `T` +- Modified parameter `getVar`: Changed type from `ReferencePackage.OpenSpiConformingClass.CustomAssociatedType` to `T` */ ``` ```swift 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 ffaa55d..492b801 100644 --- a/Tests/IntegrationTests/Resources/expected-reference-changes-swift-interface-public.md +++ b/Tests/IntegrationTests/Resources/expected-reference-changes-swift-interface-public.md @@ -1,6 +1,6 @@ -# ⚠️ 49 public changes detected ⚠️ +# ⚠️ 51 public changes detected ⚠️ _Comparing `new_public` to `old_public`_ -
❇️31 Additions
🔀16 Modifications
2 Removals
+
❇️31 Additions
🔀16 Modifications
4 Removals
--- ## `ReferencePackage` @@ -106,6 +106,13 @@ Changes: - Added generic where clause `where T : Swift.Strideable` */ ``` +#### ❌ Removed +```swift +public struct PublicStructThatIsOnlyAvailableInTheReferencePackage { + public func bar() -> Swift.Void + public var foo: Swift.String +} +``` ### `Array` #### ❇️ Added ```swift @@ -256,14 +263,25 @@ indirect case recursive(ReferencePackage.CustomEnum) /** Changes: -- Added parameter `ReferencePackage.CustomEnum` -- Removed parameter `ReferencePackage.CustomEnum` +- Modified 1st parameter: Changed type from `ReferencePackage.CustomEnum` to `ReferencePackage.CustomEnum` */ ``` #### ❌ Removed ```swift case caseWithString(Swift.String) ``` +```swift +public enum PublicEnumInExtensionOfCustomEnumThatIsOnlyAvailableInTheReferencePackage: Swift.Equatable, Swift.Hashable { + case alpha + case beta + public func hash(into hasher: inout Swift.Hasher) -> Swift.Void + public static func ==( + a: ReferencePackage.CustomEnum.PublicEnumInExtensionOfCustomEnumThatIsOnlyAvailableInTheReferencePackage, + b: ReferencePackage.CustomEnum.PublicEnumInExtensionOfCustomEnumThatIsOnlyAvailableInTheReferencePackage + ) -> Swift.Bool + public var hashValue: Swift.Int { get } +} +``` ### `CustomProtocol` #### ❇️ Added ```swift diff --git a/Tests/UnitTests/XcodeToolsTests.swift b/Tests/UnitTests/XcodeToolsTests.swift index dd50017..0735915 100644 --- a/Tests/UnitTests/XcodeToolsTests.swift +++ b/Tests/UnitTests/XcodeToolsTests.swift @@ -103,7 +103,7 @@ private extension XcodeToolsTests { var expectedHandleDebugCalls: [(message: String, subsystem: String)] = [ (archiveResult, "XcodeTools") ] - var expectedHandleFileExistsCalls = ["PROJECT_DIRECTORY_PATH/.build"] + var expectedHandleFileExistsCalls = ["PROJECT_DIRECTORY_PATH/.build/Build"] var shell = MockShell() shell.handleExecute = { command in