Skip to content
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
20 changes: 19 additions & 1 deletion Package.resolved

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

23 changes: 4 additions & 19 deletions Sources/DebugSnapshots/DebugSnapshotConvertible.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
/// This conformance is automatically applied to a type using the ``DebugSnapshot(_:)`` macro.
public protocol DebugSnapshotConvertible<DebugSnapshot> {
/// A type representing a "snapshot" of this type.
associatedtype DebugSnapshot
associatedtype DebugSnapshot: DebugSnapshotConvertible<DebugSnapshot>

static func _debugSnapshot(_ value: Self, visitor: inout _DebugSnapshotVisitor) -> DebugSnapshot
}
Expand All @@ -13,12 +13,7 @@ extension Array: DebugSnapshotConvertible where Element: DebugSnapshotConvertibl
_ value: Self,
visitor: inout _DebugSnapshotVisitor
) -> [Element.DebugSnapshot] {
var result: [Element.DebugSnapshot] = []
result.reserveCapacity(value.count)
for element in value {
result.append(Element._debugSnapshot(element, visitor: &visitor))
}
return result
value.map { Element._debugSnapshot($0, visitor: &visitor) }
}
}

Expand All @@ -27,12 +22,7 @@ extension Dictionary: DebugSnapshotConvertible where Value: DebugSnapshotConvert
_ value: Self,
visitor: inout _DebugSnapshotVisitor
) -> [Key: Value.DebugSnapshot] {
var result: [Key: Value.DebugSnapshot] = [:]
result.reserveCapacity(value.count)
for (key, value) in value {
result[key] = Value._debugSnapshot(value, visitor: &visitor)
}
return result
value.mapValues { Value._debugSnapshot($0, visitor: &visitor) }
}
}

Expand All @@ -41,12 +31,7 @@ extension Optional: DebugSnapshotConvertible where Wrapped: DebugSnapshotConvert
_ value: Self,
visitor: inout _DebugSnapshotVisitor
) -> Wrapped.DebugSnapshot? {
switch value {
case .none:
return nil
case .some(let wrapped):
return Wrapped._debugSnapshot(wrapped, visitor: &visitor)
}
value.map { Wrapped._debugSnapshot($0, visitor: &visitor) }
}
}

Expand Down
91 changes: 84 additions & 7 deletions Sources/DebugSnapshotsMacrosSupport/DebugSnapshotMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ private func structMemberDeclarations(
if hasIndirectProperties {
allConformances.append("CustomReflectable")
}
allConformances.append("\(moduleName).DebugSnapshotConvertible")
let conformancesDescription =
snapshotConformanceDescription(allConformances)
let customMirrorDecl: String
Expand All @@ -274,12 +275,28 @@ private func structMemberDeclarations(
} else {
customMirrorDecl = ""
}
let convertibleSnapshotAssignments =
properties
.filter { $0.isDebugSnapshotConvertible }
.map {
"snapshot.\($0.name) = \(moduleName)._debugSnapshot(value.\($0.name), visitor: &visitor)"
}
let snapshotBody =
convertibleSnapshotAssignments.isEmpty
? "value"
: "var snapshot = value\n\(convertibleSnapshotAssignments.joined(separator: "\n"))\nreturn snapshot"
let representation =
DeclSyntax(
"""
\(raw: propagatedAttributes.description)\
public struct DebugSnapshot\(raw: conformancesDescription) {
\(raw: propertyLines.joined(separator: "\n"))\(raw: customMirrorDecl)
public static func _debugSnapshot(\
_ value: DebugSnapshot, \
visitor: inout \(raw: moduleName)._DebugSnapshotVisitor\
) -> DebugSnapshot {
\(raw: snapshotBody)
}
}
"""
)
Expand Down Expand Up @@ -345,25 +362,48 @@ private func classMemberDeclarations(

let initParams = classInitParams(for: properties, modelName: modelDecl.name)
let snapshotInitArguments = properties.map { "\($0.name): \($0.name)" }.joined(separator: ", ")

let convertibleSnapshotAssignments =
properties
.filter { $0.isDebugSnapshotConvertible }
.map {
"snapshot.\($0.name) = \(moduleName)._debugSnapshot(value.\($0.name), visitor: &visitor)"
}
let convertibleSnapshotAssignmentsCode =
convertibleSnapshotAssignments.isEmpty
? ""
: "\n" + convertibleSnapshotAssignments.joined(separator: "\n")

let nonConvertibleInitArguments =
properties
.filter { !$0.isDebugSnapshotConvertible }
.map { "\($0.name): value.\($0.name)" }
.joined(separator: ", ")
let debugSnapshotClass =
DeclSyntax(
"""
public final class DebugSnapshot: \(raw: moduleName)._DebugSnapshotObject {
public final class DebugSnapshot: \
\(raw: moduleName)._DebugSnapshotObject, \(raw: moduleName).DebugSnapshotConvertible {
public var _snapshot: DebugSnapshotValue
public var _originIdentifier: ObjectIdentifier?
public var _diffSnapshot: (any \(raw: moduleName)._DebugSnapshotObject)?
public init(\(raw: initParams)) {
self._snapshot = DebugSnapshotValue(\(raw: snapshotInitArguments))
}
public static func _debugSnapshot(\
_ value: DebugSnapshot, \
visitor: inout \(raw: moduleName)._DebugSnapshotVisitor\
) -> DebugSnapshot {
if let existing: DebugSnapshot = visitor.lookup(value) { return existing }
let snapshot = DebugSnapshot(\(raw: nonConvertibleInitArguments))
snapshot._originIdentifier = value._originIdentifier
visitor.register(value, snapshot: snapshot)\(raw: convertibleSnapshotAssignmentsCode)
return snapshot
}
}
"""
)

let nonConvertibleInitArguments =
properties
.filter { !$0.isDebugSnapshotConvertible }
.map { "\($0.name): value.\($0.name)" }
.joined(separator: ", ")
let convertibleAssignments =
properties
.filter { $0.isDebugSnapshotConvertible }
Expand Down Expand Up @@ -555,17 +595,26 @@ private func enumMemberDeclarations(
debugSnapshotConformances(
for: declaration,
properties: []
) + propagatedAttributes.conformances
) + propagatedAttributes.conformances + ["\(moduleName).DebugSnapshotConvertible"]
)
let conformanceDescription =
snapshotConformanceDescription(debugSnapshotConformances)
let snapshotCaseLines = enumCases.map { debugSnapshotCaseDeclaration($0, modelName: name) }
let snapshotSwitchCases = enumCases.map(snapshotSwitchCase)
let representation =
DeclSyntax(
"""
\(raw: propagatedAttributes.description)\
public \(raw: isIndirect ? "indirect " : "")enum DebugSnapshot\(raw: conformanceDescription) {
\(raw: snapshotCaseLines.joined(separator: "\n"))
public static func _debugSnapshot(\
_ value: DebugSnapshot, \
visitor: inout \(raw: moduleName)._DebugSnapshotVisitor\
) -> DebugSnapshot {
switch value {
\(raw: snapshotSwitchCases.joined(separator: "\n"))
}
}
}
"""
)
Expand Down Expand Up @@ -657,6 +706,34 @@ private func debugSnapshotSwitchCase(_ enumCase: ModelDecl.EnumCase) -> String {
"""
}

private func snapshotSwitchCase(_ enumCase: ModelDecl.EnumCase) -> String {
let name = enumCase.element.name.text
let parameters = Array(enumCase.element.parameterClause?.parameters ?? [])
let bindings = parameters.indices.map { "v\($0 + 1)" }

if enumCase.isIgnored || bindings.isEmpty {
return """
case .\(name):
return .\(name)
"""
}

let pattern = ".\(name)(\(bindings.map { "let \($0)" }.joined(separator: ", ")))"
let valueArguments = zip(parameters, bindings)
.map { parameter, binding in
let mappedValue =
enumCase.isDebugSnapshotConvertible
? "\(moduleName)._debugSnapshot(\(binding), visitor: &visitor)"
: binding
return "\(caseParameterLabelPrefix(parameter))\(mappedValue)"
}
.joined(separator: ", ")
return """
case \(pattern):
return .\(name)(\(valueArguments))
"""
}

private func debugSnapshotType(_ type: TypeSyntax) -> TypeSyntax {
if let optionalType = type.trimmed.as(OptionalTypeSyntax.self) {
return TypeSyntax(
Expand Down
Loading