Skip to content

Commit f88b07c

Browse files
authored
Quality-of-life improvements (#153)
* wip * wip
1 parent a3e6de7 commit f88b07c

File tree

4 files changed

+160
-7
lines changed

4 files changed

+160
-7
lines changed

Sources/CasePaths/Never+CasePathable.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ extension Never: CasePathable {
55
}
66
}
77

8-
extension Case {
8+
extension Case where Value: CasePathable {
99
/// A case path that can never embed or extract a value.
1010
///
1111
/// This property can chain any case path into a `Never` value, which, as an uninhabited type,
@@ -15,3 +15,11 @@ extension Case {
1515
return Case<Never>(embed: absurd, extract: { (_: Value) in nil })
1616
}
1717
}
18+
19+
extension Case {
20+
@available(*, deprecated, message: "This enum must be '@CasePathable' to enable key path syntax")
21+
public var never: Case<Never> {
22+
func absurd<T>(_: Never) -> T {}
23+
return Case<Never>(embed: absurd, extract: { (_: Value) in nil })
24+
}
25+
}

Sources/CasePathsMacros/CasePathableMacro.swift

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,8 @@ extension CasePathableMacro: MemberMacro {
9999
enumName: TokenSyntax
100100
) -> [DeclSyntax] {
101101
elements.flatMap {
102-
if let elements = $0.decl.as(EnumCaseDeclSyntax.self)?.elements {
103-
return generateDeclSyntax(from: elements, enumName: enumName)
102+
if let decl = $0.decl.as(EnumCaseDeclSyntax.self) {
103+
return generateDeclSyntax(from: decl, enumName: enumName)
104104
}
105105
if let ifConfigDecl = $0.decl.as(IfConfigDeclSyntax.self) {
106106
let ifClauses = ifConfigDecl.clauses.flatMap { decl -> [DeclSyntax] in
@@ -118,10 +118,10 @@ extension CasePathableMacro: MemberMacro {
118118
}
119119

120120
static func generateDeclSyntax(
121-
from enumCaseDecls: EnumCaseElementListSyntax,
121+
from decl: EnumCaseDeclSyntax,
122122
enumName: TokenSyntax
123123
) -> [DeclSyntax] {
124-
enumCaseDecls.map {
124+
decl.elements.map {
125125
let caseName = $0.name.trimmed
126126
let associatedValueName = $0.trimmedTypeDescription
127127
let hasPayload = $0.parameterClause.map { !$0.parameters.isEmpty } ?? false
@@ -137,9 +137,18 @@ extension CasePathableMacro: MemberMacro {
137137
bindingNames = ""
138138
returnName = "()"
139139
}
140-
140+
let leadingTriviaLines = decl.leadingTrivia.description
141+
.drop(while: \.isNewline)
142+
.split(separator: "\n", omittingEmptySubsequences: false)
143+
let indent = leadingTriviaLines
144+
.compactMap { $0.isEmpty ? nil : $0.prefix(while: \.isWhitespace).count }
145+
.min(by: { (lhs: Int, rhs: Int) -> Bool in lhs == 0 ? lhs > rhs : lhs < rhs })
146+
?? 0
147+
let leadingTrivia = leadingTriviaLines
148+
.map { String($0.dropFirst(indent)) }
149+
.joined(separator: "\n")
141150
return """
142-
public var \(caseName): \
151+
\(raw: leadingTrivia)public var \(caseName): \
143152
\(raw: qualifiedCasePathTypeName)<\(enumName), \(raw: associatedValueName)> {
144153
\(raw: qualifiedCasePathTypeName)<\(enumName), \(raw: associatedValueName)>(
145154
embed: \(raw: hasPayload ? "\(enumName).\(caseName)" : "{ \(enumName).\(caseName) }"),

Tests/CasePaths copy.xctestplan

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"configurations" : [
3+
{
4+
"id" : "2400C241-35D6-4B6B-B6EA-A4105C2738D6",
5+
"name" : "Configuration 1",
6+
"options" : {
7+
8+
}
9+
}
10+
],
11+
"defaultOptions" : {
12+
13+
},
14+
"testTargets" : [
15+
16+
],
17+
"version" : 1
18+
}

Tests/CasePathsMacrosTests/CasePathableMacroTests.swift

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -603,4 +603,122 @@ final class CasePathableMacroTests: XCTestCase {
603603
"""
604604
}
605605
}
606+
607+
func testDocumentation() {
608+
assertMacro {
609+
"""
610+
@CasePathable
611+
enum Foo {
612+
613+
/// The bar case.
614+
case bar
615+
616+
/// The baz case.
617+
///
618+
/// A case for baz.
619+
case baz
620+
621+
/**
622+
The fizz buzz case.
623+
624+
A case for fizz and buzz.
625+
*/
626+
case fizz, buzz
627+
}
628+
"""
629+
} expansion: {
630+
"""
631+
enum Foo {
632+
633+
/// The bar case.
634+
case bar
635+
636+
/// The baz case.
637+
///
638+
/// A case for baz.
639+
case baz
640+
641+
/**
642+
The fizz buzz case.
643+
644+
A case for fizz and buzz.
645+
*/
646+
case fizz, buzz
647+
648+
public struct AllCasePaths {
649+
/// The bar case.
650+
public var bar: CasePaths.AnyCasePath<Foo, Void> {
651+
CasePaths.AnyCasePath<Foo, Void>(
652+
embed: {
653+
Foo.bar
654+
},
655+
extract: {
656+
guard case .bar = $0 else {
657+
return nil
658+
}
659+
return ()
660+
}
661+
)
662+
}
663+
/// The baz case.
664+
///
665+
/// A case for baz.
666+
public var baz: CasePaths.AnyCasePath<Foo, Void> {
667+
CasePaths.AnyCasePath<Foo, Void>(
668+
embed: {
669+
Foo.baz
670+
},
671+
extract: {
672+
guard case .baz = $0 else {
673+
return nil
674+
}
675+
return ()
676+
}
677+
)
678+
}
679+
/**
680+
The fizz buzz case.
681+
682+
A case for fizz and buzz.
683+
*/
684+
public var fizz: CasePaths.AnyCasePath<Foo, Void> {
685+
CasePaths.AnyCasePath<Foo, Void>(
686+
embed: {
687+
Foo.fizz
688+
},
689+
extract: {
690+
guard case .fizz = $0 else {
691+
return nil
692+
}
693+
return ()
694+
}
695+
)
696+
}
697+
/**
698+
The fizz buzz case.
699+
700+
A case for fizz and buzz.
701+
*/
702+
public var buzz: CasePaths.AnyCasePath<Foo, Void> {
703+
CasePaths.AnyCasePath<Foo, Void>(
704+
embed: {
705+
Foo.buzz
706+
},
707+
extract: {
708+
guard case .buzz = $0 else {
709+
return nil
710+
}
711+
return ()
712+
}
713+
)
714+
}
715+
}
716+
public static var allCasePaths: AllCasePaths { AllCasePaths() }
717+
}
718+
719+
extension Foo: CasePaths.CasePathable {
720+
}
721+
"""
722+
}
723+
}
606724
}

0 commit comments

Comments
 (0)