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

Adding support for more declaration attributes #6

Merged
merged 5 commits into from
Aug 19, 2024
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
2 changes: 1 addition & 1 deletion .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Detect public API changes
name: 🧪 Run Tests

on:
pull_request:
Expand Down
Binary file removed Sources/.DS_Store
Binary file not shown.
Binary file removed Sources/Helpers/.DS_Store
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,6 @@ private extension SDKDump.FunctionElement {
}

func extractArguments() -> [Argument] {
// TODO: Add support for: class func, rethrows, async
// See: https://docs.swift.org/swift-book/documentation/the-swift-programming-language/declarations#Enumerations-with-Indirection

let parameterNames = Self.parameterNames(
from: underlyingElement.printedName,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//
// Copyright (c) 2024 Adyen N.V.
//
// This file is open source and available under the MIT license. See the LICENSE file for more info.
//

import Foundation

extension SDKDump.Element {

public var asOperator: SDKDump.OperatorElement? {
.init(for: self)
}
}

extension SDKDump {

struct OperatorElement: CustomStringConvertible {

public var declaration: String { "operator" }

public var name: String { underlyingElement.printedName }

public var description: String { "\(declaration) \(name)" }

private let underlyingElement: SDKDump.Element

fileprivate init?(for underlyingElement: SDKDump.Element) {
guard underlyingElement.declKind == .infixOperator || underlyingElement.declKind == .postfixOperator || underlyingElement.declKind == .prefixOperator else { return nil }

self.underlyingElement = underlyingElement
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,8 @@ extension SDKDump {

enum DeclarationKind: String, RawRepresentable, Codable {

// TODO: Add support for: Actor, Operator, PrecedenceGroup,
// See: https://docs.swift.org/swift-book/documentation/the-swift-programming-language/declarations#Actor-Declaration
// See: https://docs.swift.org/swift-book/documentation/the-swift-programming-language/declarations#Operator-Declaration
// See: https://docs.swift.org/swift-book/documentation/the-swift-programming-language/declarations#Precedence-Group-Declaration

case `import` = "Import"
case `class` = "Class"
case `class` = "Class" // An `actor` is also just a `class` with some protocol conformances
case `struct` = "Struct"
case `enum` = "Enum"
case `case` = "EnumElement"
Expand All @@ -33,5 +28,9 @@ extension SDKDump {
case associatedType = "AssociatedType"

case macro = "Macro"

case infixOperator = "InfixOperator"
case postfixOperator = "PostfixOperator"
case prefixOperator = "PrefixOperator"
}
}
19 changes: 17 additions & 2 deletions Sources/Helpers/Models/SDKDump/SDKDump+Element+Description.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ extension SDKDump.Element: CustomStringConvertible {
return self.asFunction?.description ?? defaultVerboseName
case .typeAlias:
return self.asTypeAlias?.description ?? defaultVerboseName
case .infixOperator, .prefixOperator, .postfixOperator:
return self.asOperator?.description ?? defaultVerboseName
}
}
}
Expand All @@ -52,14 +54,27 @@ private extension SDKDump.Element {
spiGroupNames?.forEach { components += ["@_spi(\($0))"] }
if hasDiscardableResult { components += ["@discardableResult"] }
if isObjcAccessible { components += ["@objc"] }
if isInlinable { components += ["@inlinable"] }
if isOverride { components += ["override"] }
if declKind != .import { components += [isInternal ? "internal" : "public"] }
if declKind != .import {
if isOpen {
components += ["open"]
} else if isInternal {
components += ["internal"]
} else {
components += ["public"]
}
}
if isFinal { components += ["final"] }
if isIndirect { components += ["indirect"] }
if isRequired { components += ["required"] }
if isStatic { components += ["static"] }
if isConvenienceInit { components += ["convenience"] }
if isDynamic { components += ["dynamic"] }

if isPrefix { components += ["prefix"] }
if isPostfix { components += ["postfix"] }
if isInfix { components += ["infix"] }

return components
}

Expand Down
124 changes: 54 additions & 70 deletions Sources/Helpers/Models/SDKDump/SDKDump+Element+Difference.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ extension SDKDump.Element {
diff += differences(toIsDynamic: otherElement.isDynamic)
diff += differences(toIsMutating: otherElement.isMutating)
diff += differences(toIsRequired: otherElement.isRequired)
diff += differences(toIsOpen: otherElement.isOpen)
diff += differences(toIsInternal: otherElement.isInternal)
diff += differences(toIsPrefix: otherElement.isPrefix)
diff += differences(toIsInfix: otherElement.isInfix)
diff += differences(toIsPostfix: otherElement.isPostfix)
diff += differences(toIsInlinable: otherElement.isInlinable)
diff += differences(toIsIndirect: otherElement.isIndirect)

if let functionDescription = asFunction, let otherFunctionDescription = otherElement.asFunction {
diff += functionDescription.differences(toFunction: otherFunctionDescription)
Expand All @@ -34,22 +41,12 @@ private extension SDKDump.Element {

func differences(toIsFinal otherIsFinal: Bool) -> [String] {
guard isFinal != otherIsFinal else { return [] }

if otherIsFinal {
return ["Added `final` keyword"]
} else {
return ["Removed `final` keyword"]
}
return ["\(otherIsFinal ? "Added" : "Removed") `final` keyword"]
}

func differences(toIsThrowing otherIsThrowing: Bool) -> [String] {
guard isThrowing != otherIsThrowing else { return [] }

if otherIsThrowing {
return ["Added `throws` keyword"]
} else {
return ["Removed `throws` keyword"]
}
return ["\(otherIsThrowing ? "Added" : "Removed") `throws` keyword"]
}

func differences(toSpiGroupNames otherSpiGroupNames: [String]?) -> [String] {
Expand All @@ -59,11 +56,7 @@ private extension SDKDump.Element {
let otherSpiGroupNames = Set(otherSpiGroupNames ?? [])

return ownSpiGroupNames.symmetricDifference(otherSpiGroupNames).map {
if otherSpiGroupNames.contains($0) {
return "Added `@_spi(\($0))`"
} else {
return "Removed `@_spi(\($0))`"
}
"\(otherSpiGroupNames.contains($0) ? "Added" : "Removed") `@_spi(\($0))`"
}
}

Expand All @@ -74,11 +67,7 @@ private extension SDKDump.Element {
let otherConformances = Set(otherConformances ?? [])

return ownConformances.symmetricDifference(otherConformances).map {
if otherConformances.contains($0) {
return "Added `\($0.printedName)` conformance"
} else {
return "Removed `\($0.printedName)` conformance"
}
"\(otherConformances.contains($0) ? "Added" : "Removed") `\($0.printedName)` conformance"
}
}

Expand All @@ -89,22 +78,13 @@ private extension SDKDump.Element {
let otherAccessors = Set(otherAccessors?.map(\.printedName) ?? [])

return ownAccessors.symmetricDifference(otherAccessors).map {
if otherAccessors.contains($0) {
return "Added `\($0)` accessor"
} else {
return "Removed `\($0)` accessor"
}
"\(otherAccessors.contains($0) ? "Added" : "Removed") `\($0)` accessor"
}
}

func differences(toHasDiscardableResult otherHasDiscardableResult: Bool) -> [String] {
guard hasDiscardableResult != otherHasDiscardableResult else { return [] }

if otherHasDiscardableResult {
return ["Added `@discardableResult` keyword"]
} else {
return ["Removed `@discardableResult` keyword"]
}
return ["\(otherHasDiscardableResult ? "Added" : "Removed") `@discardableResult` keyword"]
}

func differences(toInitKind otherInitKind: String?) -> [String] {
Expand Down Expand Up @@ -133,52 +113,62 @@ private extension SDKDump.Element {

func differences(toIsObjcAccessible otherIsObjcAccessible: Bool) -> [String] {
guard isObjcAccessible != otherIsObjcAccessible else { return [] }

if otherIsObjcAccessible {
return ["Added `@objc` keyword"]
} else {
return ["Removed `@objc` keyword"]
}
return ["\(otherIsObjcAccessible ? "Added" : "Removed") `@objc` keyword"]
}

func differences(toIsOverride otherIsOverride: Bool) -> [String] {
guard isOverride != otherIsOverride else { return [] }

if otherIsOverride {
return ["Added `override` keyword"]
} else {
return ["Removed `override` keyword"]
}
return ["\(otherIsOverride ? "Added" : "Removed") `override` keyword"]
}

func differences(toIsDynamic otherIsDynamic: Bool) -> [String] {
guard isDynamic != otherIsDynamic else { return [] }

if otherIsDynamic {
return ["Added `dynamic` keyword"]
} else {
return ["Removed `dynamic` keyword"]
}
return ["\(otherIsDynamic ? "Added" : "Removed") `dynamic` keyword"]
}

func differences(toIsMutating otherIsMutating: Bool) -> [String] {
guard isMutating != otherIsMutating else { return [] }

if otherIsMutating {
return ["Added `mutating` keyword"]
} else {
return ["Removed `mutating` keyword"]
}
return ["\(otherIsMutating ? "Added" : "Removed") `mutating` keyword"]
}

func differences(toIsRequired otherIsRequired: Bool) -> [String] {
guard isRequired != otherIsRequired else { return [] }

if otherIsRequired {
return ["Added `required` keyword"]
} else {
return ["Removed `required` keyword"]
}
return ["\(otherIsRequired ? "Added" : "Removed") `required` keyword"]
}

func differences(toIsOpen otherIsOpen: Bool) -> [String] {
guard isOpen != otherIsOpen else { return [] }
return ["\(otherIsOpen ? "Added" : "Removed") `open` keyword"]
}

func differences(toIsInternal otherIsInternal: Bool) -> [String] {
guard isInternal != otherIsInternal else { return [] }
return ["\(otherIsInternal ? "Added" : "Removed") `internal` keyword"]
}

func differences(toIsPrefix otherIsPrefix: Bool) -> [String] {
guard isPrefix != otherIsPrefix else { return [] }
return ["\(otherIsPrefix ? "Added" : "Removed") `prefix` keyword"]
}

func differences(toIsPostfix otherIsPostfix: Bool) -> [String] {
guard isPostfix != otherIsPostfix else { return [] }
return ["\(otherIsPostfix ? "Added" : "Removed") `postfix` keyword"]
}

func differences(toIsInfix otherIsInfix: Bool) -> [String] {
guard isInfix != otherIsInfix else { return [] }
return ["\(otherIsInfix ? "Added" : "Removed") `infix` keyword"]
}

func differences(toIsInlinable otherIsInlinable: Bool) -> [String] {
guard isInlinable != otherIsInlinable else { return [] }
return ["\(otherIsInlinable ? "Added" : "Removed") `@inlinable` keyword"]
}

func differences(toIsIndirect otherIsIndirect: Bool) -> [String] {
guard isIndirect != otherIsIndirect else { return [] }
return ["\(otherIsIndirect ? "Added" : "Removed") `indirect` keyword"]
}
}

Expand All @@ -190,17 +180,11 @@ extension SDKDump.FunctionElement {

guard ownArguments != otherArguments else { return [] }

// TODO: Indicate more in depth if the order, type and/or default arg changed

let ownArgumentNames = Set(arguments.map(\.description))
let otherArgumentNames = Set(otherArguments.map(\.description))

return ownArgumentNames.symmetricDifference(otherArgumentNames).map {
if otherArgumentNames.contains($0) {
return "Added `\($0)`"
} else {
return "Removed `\($0)`"
}
"\(otherArgumentNames.contains($0) ? "Added" : "Removed") `\($0)`"
}
}
}
30 changes: 28 additions & 2 deletions Sources/Helpers/Models/SDKDump/SDKDump+Element.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@ extension SDKDump {
let isLet: Bool
/// Indicates whether or not a function argument has a default value
let hasDefaultArg: Bool
/// Whether or not an element is marked as internal (only observed when using an abi.json from a binary framework)
/// Whether or not an element is marked as `package` or `internal` (only observed when using an abi.json from a binary framework)
let isInternal: Bool
/// Whether or not a class is marked as `open`
let isOpen: Bool
/// Indicates whether or not a function is throwing
let isThrowing: Bool
/// Indicates whether or not an init is a `convenience` or a `designated` initializer
Expand Down Expand Up @@ -63,6 +65,7 @@ extension SDKDump {
hasDefaultArg: Bool = false,
isInternal: Bool = false,
isThrowing: Bool = false,
isOpen: Bool = false,
initKind: String? = nil,
genericSig: String? = nil,
paramValueOwnership: String? = nil,
Expand All @@ -83,6 +86,7 @@ extension SDKDump {
self.hasDefaultArg = hasDefaultArg
self.isInternal = isInternal
self.isThrowing = isThrowing
self.isOpen = isOpen
self.children = children
self.spiGroupNames = spiGroupNames
self.declAttributes = declAttributes
Expand All @@ -108,6 +112,7 @@ extension SDKDump {
self.hasDefaultArg = (try? container.decode(Bool.self, forKey: CodingKeys.hasDefaultArg)) ?? false
self.isInternal = (try? container.decode(Bool.self, forKey: CodingKeys.isInternal)) ?? false
self.isThrowing = (try? container.decode(Bool.self, forKey: CodingKeys.isThrowing)) ?? false
self.isOpen = try container.decodeIfPresent(Bool.self, forKey: CodingKeys.isOpen) ?? false
self.declAttributes = try container.decodeIfPresent([String].self, forKey: CodingKeys.declAttributes)
self.conformances = try container.decodeIfPresent([Conformance].self, forKey: CodingKeys.conformances)
self.accessors = try container.decodeIfPresent([SDKDump.Element].self, forKey: CodingKeys.accessors)
Expand All @@ -130,6 +135,7 @@ extension SDKDump {
try container.encode(self.hasDefaultArg, forKey: SDKDump.Element.CodingKeys.hasDefaultArg)
try container.encode(self.isInternal, forKey: SDKDump.Element.CodingKeys.isInternal)
try container.encode(self.isThrowing, forKey: SDKDump.Element.CodingKeys.isThrowing)
try container.encode(self.isOpen, forKey: SDKDump.Element.CodingKeys.isOpen)
try container.encodeIfPresent(self.declAttributes, forKey: SDKDump.Element.CodingKeys.declAttributes)
try container.encodeIfPresent(self.conformances, forKey: SDKDump.Element.CodingKeys.conformances)
try container.encodeIfPresent(self.accessors, forKey: SDKDump.Element.CodingKeys.accessors)
Expand Down Expand Up @@ -158,9 +164,9 @@ extension SDKDump {
case genericSig
case paramValueOwnership
case funcSelfKind
case isOpen
}

// Custom conformance as we have to use a `class` for the `Element`
static func == (lhs: SDKDump.Element, rhs: SDKDump.Element) -> Bool {
lhs.kind == rhs.kind &&
lhs.name == rhs.name &&
Expand Down Expand Up @@ -232,6 +238,26 @@ extension SDKDump.Element {
(declAttributes ?? []).contains("Required")
}

var isPrefix: Bool {
(declAttributes ?? []).contains("Prefix")
}

var isPostfix: Bool {
(declAttributes ?? []).contains("Postfix")
}

var isInfix: Bool {
(declAttributes ?? []).contains("Infix")
}

var isInlinable: Bool {
(declAttributes ?? []).contains("Inlinable")
}

var isIndirect: Bool {
(declAttributes ?? []).contains("Indirect")
}

var isTypeInformation: Bool {
kind.isTypeInformation
}
Expand Down
Loading
Loading