Skip to content

Commit

Permalink
Add diff helper
Browse files Browse the repository at this point in the history
- A new diff helper has been added for common comparator operations of diffing common elements
- The helper introduces a new `DiffComparable` protocol which descriptors can conform to
- Where possible comparators and descriptors were migrated to leverage it

Test Plan:

- Verify all unit and integration tests continue to pass (i.e. no diff in output format)

Signed-off-by: Kassem Wridan <[email protected]>
  • Loading branch information
kwridan committed Nov 11, 2023
1 parent 2b7edf9 commit 603936f
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 157 deletions.
55 changes: 18 additions & 37 deletions Sources/XCDiffCore/Comparator/HeadersComparator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,50 +30,31 @@ final class HeadersComparator: Comparator {
let firstHeaders = try targetsHelper.headers(from: firstTarget, sourceRoot: first.sourceRoot)
let secondHeaders = try targetsHelper.headers(from: secondTarget, sourceRoot: second.sourceRoot)

let firstPaths = Set(firstHeaders.map { $0.path })
let secondPaths = Set(secondHeaders.map { $0.path })

let commonHeaders = commonHeaderDescriptorPairs(first: firstHeaders, second: secondHeaders)
let differentValues = attributesDifferences(in: commonHeaders)

return result(context: ["\"\(secondTarget.name)\" target"],
first: firstPaths,
second: secondPaths,
differentValues: differentValues)
return result(
context: ["\"\(secondTarget.name)\" target"],
first: firstHeaders,
second: secondHeaders,
diffCommonValues: { commonPairs in
attributesDifferences(in: commonPairs)
}
)
}
}

// MARK: - Private

/// Returns common header descriptors as a header descriptor pair
private func commonHeaderDescriptorPairs(first: [HeaderDescriptor],
second: [HeaderDescriptor]) -> [HeaderDescriptorPair] {
let firstHeaderDescriptorMap = headerPathMap(from: first)
let secondHeaderDescriptorMap = headerPathMap(from: second)

let firstPaths = Set(firstHeaderDescriptorMap.keys)
let secondPaths = Set(secondHeaderDescriptorMap.keys)

let commonSources = firstPaths
.intersection(secondPaths)
.map { (firstHeaderDescriptorMap[$0]!, secondHeaderDescriptorMap[$0]!) }
.sorted { left, right in left.0.path < right.0.path }

return commonSources
}

/// Returns attributes differences between header pairs
private func attributesDifferences(in headerDescriptorPairs: [HeaderDescriptorPair])
-> [CompareResult.DifferentValues] {
private func attributesDifferences(
in headerDescriptorPairs: [HeaderDescriptorPair]
) -> [CompareResult.DifferentValues] {
return headerDescriptorPairs
.filter { $0.attributes != $1.attributes }
.map { first, second in CompareResult.DifferentValues(context: "\(first.path) attributes",
first: first.attributes ?? "nil (Project)",
second: second.attributes ?? "nil (Project)") }
}

/// Returns a dictionary that maps header descriptors by their path `[path: HeaderDescriptor]`
private func headerPathMap(from headerDescriptors: [HeaderDescriptor]) -> [String: HeaderDescriptor] {
return Dictionary(headerDescriptors.map { ($0.path, $0) }, uniquingKeysWith: { first, _ in first })
.map { first, second in
CompareResult.DifferentValues(
context: "\(first.path) attributes",
first: first.attributes ?? "nil (Project)",
second: second.attributes ?? "nil (Project)"
)
}
}
}
69 changes: 17 additions & 52 deletions Sources/XCDiffCore/Comparator/LinkedDependenciesComparator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,56 +37,30 @@ final class LinkedDependenciesComparator: Comparator {
}

private func createLinkedDependenciesResults(commonTarget: TargetPair) throws -> CompareResult {
let firstDependencies = try targetsHelper.linkedDependencies(from: commonTarget.first)
let secondDependencies = try targetsHelper.linkedDependencies(from: commonTarget.second)

let firstPaths = Set(firstDependencies.compactMap { dependencyKey(dependency: $0) })
let secondPaths = Set(secondDependencies.compactMap { dependencyKey(dependency: $0) })

let descriptorPairs = commonDependencyDescriptorPairs(first: firstDependencies,
second: secondDependencies)

let attributesDifferences = self.attributesDifferences(in: descriptorPairs)
let packagesDifferences = packageDifferences(in: descriptorPairs)

return result(context: ["\"\(commonTarget.first.name)\" target"],
first: firstPaths,
second: secondPaths,
differentValues: attributesDifferences + packagesDifferences)
}

private func dependencyKey(dependency: LinkedDependencyDescriptor) -> String? {
if let key = dependency.name ?? dependency.path { return key }
return nil
}

private func commonDependencyDescriptorPairs(first: [LinkedDependencyDescriptor],
second: [LinkedDependencyDescriptor]) -> [DependencyDescriptorPair] {
let firstDependencyDescriptorMap = dependencyPathMap(from: first)
let secondDependencyDescriptorMap = dependencyPathMap(from: second)

let firstPaths = Set(firstDependencyDescriptorMap.keys)
let secondPaths = Set(secondDependencyDescriptorMap.keys)

let commonSources = firstPaths
.intersection(secondPaths)
.map { (firstDependencyDescriptorMap[$0]!, secondDependencyDescriptorMap[$0]!) }
.sorted { left, right in
if let keyLeft = left.0.name ?? left.0.path,
let keyRight = right.0.name ?? right.0.path {
return keyLeft < keyRight
}
return false
let firstDependencies = try targetsHelper
.linkedDependencies(from: commonTarget.first)
.filter { $0.key != nil }
let secondDependencies = try targetsHelper
.linkedDependencies(from: commonTarget.second)
.filter { $0.key != nil }

return result(
context: ["\"\(commonTarget.first.name)\" target"],
first: firstDependencies,
second: secondDependencies,
diffCommonValues: { commonPairs in
attributesDifferences(in: commonPairs)
+ packageDifferences(in: commonPairs)
}
return commonSources
)
}

private func attributesDifferences(in dependencyDescriptorPairs: [DependencyDescriptorPair])
-> [CompareResult.DifferentValues] {
return dependencyDescriptorPairs
.filter { $0.type != $1.type }
.compactMap { first, second -> CompareResult.DifferentValues? in
if let key = dependencyKey(dependency: first) {
if let key = first.key {
return .init(context: "\(key) attributes",
first: first.type.rawValue,
second: second.type.rawValue)
Expand All @@ -100,21 +74,12 @@ final class LinkedDependenciesComparator: Comparator {
return dependencyDescriptorPairs
.filter { $0.package != $1.package }
.compactMap { first, second -> CompareResult.DifferentValues? in
if let key = dependencyKey(dependency: first) {
if let key = first.key {
return .init(context: "\(key) package reference",
first: first.package?.difference(from: second.package),
second: second.package?.difference(from: first.package))
}
return nil
}
}

private func dependencyPathMap(from dependencyDescriptors: [LinkedDependencyDescriptor])
-> [String: LinkedDependencyDescriptor] {
return Dictionary(dependencyDescriptors.compactMap {
if let key = dependencyKey(dependency: $0) { return (key, $0) }
return nil
},
uniquingKeysWith: { first, _ in first })
}
}
58 changes: 21 additions & 37 deletions Sources/XCDiffCore/Comparator/SourcesComparator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,58 +22,42 @@ final class SourcesComparator: Comparator {
let tag = "sources"
private let targetsHelper = TargetsHelper()

func compare(_ first: ProjectDescriptor,
_ second: ProjectDescriptor,
parameters: ComparatorParameters) throws -> [CompareResult] {
func compare(
_ first: ProjectDescriptor,
_ second: ProjectDescriptor,
parameters: ComparatorParameters
) throws -> [CompareResult] {
let commonTargets = try targetsHelper.commonTargets(first, second, parameters: parameters)
return try commonTargets
.map { firstTarget, secondTarget -> CompareResult in
let firstSources = try targetsHelper.sources(from: firstTarget, sourceRoot: first.sourceRoot)
let secondSources = try targetsHelper.sources(from: secondTarget, sourceRoot: second.sourceRoot)

let firstPaths = Set(firstSources.map { $0.path })
let secondPaths = Set(secondSources.map { $0.path })

let commonSources = commonSourcesPair(first: firstSources, second: secondSources)
let difference = compilerFlagDifferences(in: commonSources)

return result(context: ["\"\(firstTarget.name)\" target"],
first: firstPaths,
second: secondPaths,
differentValues: difference)
return result(
context: ["\"\(firstTarget.name)\" target"],
first: firstSources,
second: secondSources,
diffCommonValues: { commonPairs in
compilerFlagDifferences(in: commonPairs)
}
)
}
}

/// Returns common sources as a source pair
private func commonSourcesPair(first: [SourceDescriptor], second: [SourceDescriptor]) -> [SourcePair] {
let firstSourcesMapping = sourcePathMapping(from: first)
let secondSourcesMapping = sourcePathMapping(from: second)

let firstPaths = Set(firstSourcesMapping.keys)
let secondPaths = Set(secondSourcesMapping.keys)

let commonSources: [SourcePair] = firstPaths.intersection(secondPaths).map {
(firstSourcesMapping[$0]!, secondSourcesMapping[$0]!)
}

return commonSources
}

/// Returns compiler flag differences between source pairs
private func compilerFlagDifferences(in sourcePairs: [SourcePair]) -> [CompareResult.DifferentValues] {
private func compilerFlagDifferences(
in sourcePairs: [SourcePair]
) -> [CompareResult.DifferentValues] {
return sourcePairs.compactMap { sourcePair in
let (first, second) = sourcePair
if first.flags != second.flags {
return CompareResult.DifferentValues(context: "\(first.path) compiler flags",
first: first.flags,
second: second.flags)
return CompareResult.DifferentValues(
context: "\(first.path) compiler flags",
first: first.flags,
second: second.flags
)
}
return nil
}
}

/// Returns a dictionary that maps sources by their path `[path: SourceDescriptor]`
private func sourcePathMapping(from sources: [SourceDescriptor]) -> [String: SourceDescriptor] {
return Dictionary(sources.map { ($0.path, $0) }, uniquingKeysWith: { first, _ in first })
}
}
42 changes: 14 additions & 28 deletions Sources/XCDiffCore/Comparator/SwiftPackagesComparator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,35 +28,21 @@ final class SwiftPackagesComparator: Comparator {
let firstPackages = try targetsHelper.swiftPackages(in: first)
let secondPackages = try targetsHelper.swiftPackages(in: second)

let firstDictionary = Dictionary(firstPackages.map { ($0.identifier, $0) }, uniquingKeysWith: { $1 })
let secondDictionary = Dictionary(secondPackages.map { ($0.identifier, $0) }, uniquingKeysWith: { $1 })

let onlyInFirst = firstPackages.filter { secondDictionary[$0.identifier] == nil }
let onlyInSecond = secondPackages.filter { firstDictionary[$0.identifier] == nil }
let common = firstPackages.filter { secondDictionary[$0.identifier] != nil }
let differences = common.filter {
firstDictionary[$0.identifier] != secondDictionary[$0.identifier]
}

let differencesValues: [CompareResult.DifferentValues] = differences.compactMap {
guard let first = firstDictionary[$0.identifier],
let second = secondDictionary[$0.identifier] else {
return nil
}

return CompareResult.DifferentValues(
context: "\($0.name) (\($0.url))",
first: first.difference(from: second),
second: second.difference(from: first)
)
}

return [
CompareResult(
tag: tag,
onlyInFirst: onlyInFirst.map(\.description),
onlyInSecond: onlyInSecond.map(\.description),
differentValues: differencesValues
result(
first: firstPackages,
second: secondPackages,
diffCommonValues: { commonPairs in
commonPairs.filter {
$0 != $1
}.map { first, second in
CompareResult.DifferentValues(
context: "\(first.name) (\(first.url))",
first: first.difference(from: second),
second: second.difference(from: first)
)
}
}
),
]
}
Expand Down
55 changes: 55 additions & 0 deletions Sources/XCDiffCore/Library/Comparator+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@

import Foundation

protocol DiffComparable: Hashable {
var diffKey: String { get }
var diffDescription: String { get }
}

extension DiffComparable {
var diffDescription: String { diffKey }
}

extension Comparator {
func result(context: [String] = [],
description: String? = nil,
Expand Down Expand Up @@ -78,4 +87,50 @@ extension Comparator {
second: second,
differentValues: differentValues)]
}

typealias DiffCommonValues<T: DiffComparable> = (
[(first: T, second: T)]
) -> [CompareResult.DifferentValues]

func result<T: DiffComparable>(
context: [String] = [],
description: String? = nil,
first: [T] = [],
second: [T] = [],
diffCommonValues: DiffCommonValues<T> = { _ in [] }

Check warning on line 100 in Sources/XCDiffCore/Library/Comparator+Extensions.swift

View check run for this annotation

Codecov / codecov/patch

Sources/XCDiffCore/Library/Comparator+Extensions.swift#L100

Added line #L100 was not covered by tests
) -> CompareResult {
let firstKeys = Set(first.map(\.diffKey))
let secondKeys = Set(second.map(\.diffKey))

let firstMap = Dictionary(
first.map { ($0.diffKey, $0) },
uniquingKeysWith: { $1 }
)

let secondMap = Dictionary(
second.map { ($0.diffKey, $0) },
uniquingKeysWith: { $1 }
)

let common = firstKeys
.intersection(secondKeys)
.sorted()
.map { key in
let commonFirst = firstMap[key]!
let commonSecond = secondMap[key]!
return (first: commonFirst, second: commonSecond)
}

return result(
context: context,
description: description,
onlyInFirst: firstKeys.subtractingAndSorted(secondKeys).compactMap {
firstMap[$0]?.diffDescription
},
onlyInSecond: secondKeys.subtractingAndSorted(firstKeys).compactMap {
secondMap[$0]?.diffDescription
},
differentValues: diffCommonValues(common)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,13 @@ struct SwiftPackageDescriptor: Hashable, CustomStringConvertible, Comparable {
return lhs.description < rhs.description
}
}

extension SwiftPackageDescriptor: DiffComparable {
var diffKey: String {
identifier
}

var diffDescription: String {
description
}
}
Loading

0 comments on commit 603936f

Please sign in to comment.