Skip to content

Commit 79d9274

Browse files
GottaGetSwiftyPJ Fechner
and
PJ Fechner
authored
Adding missing tests and some minor refactoring (#57)
Adding missing tests and some minor refactoring Co-authored-by: PJ Fechner <PJ@getswifty.blog>
1 parent e44fd94 commit 79d9274

16 files changed

+376
-65
lines changed

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ let package = Package(
3131
// .package(url: "https://github.com/realm/SwiftLint.git", .upToNextMajor(from: "0.52.0")),
3232
.package(url: "https://github.com/swiftlang/swift-syntax.git", "508.0.0"..."600.0.1"),
3333
// For testing different versions of swift-syntax
34-
// .package(url: "https://github.com/apple/swift-syntax.git", .upToNextMajor(from: "509.0.0"))
34+
// .package(url: "https://github.com/apple/swift-syntax.git", .upToNextMajor(from: "510.0.0"))
3535

3636
],
3737
targets: [

Sources/CodableWrapperMacros/CodingKeys/CodingKeyTypes.swift

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -82,35 +82,43 @@ enum CodingKeyCase {
8282
/// custom casing
8383
case custom((String) -> (String))
8484

85-
var separator: String {
85+
var separator: String? {
8686
switch self {
8787
case .noChanges, .camelCase, .flatCase, .pascalCase, .upperCase:
8888
""
8989
case .snakeCase, .camelSnakeCase, .pascalSnakeCase, .screamingSnakeCase:
9090
"_"
9191
case .kebabCase, .camelKebabCase, .pascalKebabCase, .screamingKebabCase:
9292
"-"
93-
case .custom(_):
94-
""
93+
case .custom:
94+
nil
95+
}
96+
}
97+
98+
var caseVariant: CaseVariant? {
99+
switch self {
100+
case .flatCase, .snakeCase, .kebabCase:
101+
.lowerCase
102+
case .camelCase, .camelSnakeCase, .camelKebabCase:
103+
.camelCase
104+
case .pascalCase, .pascalSnakeCase, .pascalKebabCase:
105+
.pascalCase
106+
case .upperCase, .screamingSnakeCase, .screamingKebabCase:
107+
.upperCase
108+
case .custom(_), .noChanges:
109+
nil
95110
}
96111
}
97112

98113
func makeKeyValue(from value: String) -> String {
99114
switch self {
100-
case .noChanges: value
101-
case .camelCase: KeyConverter.plainCaseConverter.convert(value: value, variant: .camelCase)
102-
case .flatCase: KeyConverter.plainCaseConverter.convert(value: value, variant: .lowerCase)
103-
case .pascalCase: KeyConverter.plainCaseConverter.convert(value: value, variant: .pascalCase)
104-
case .upperCase: KeyConverter.plainCaseConverter.convert(value: value, variant: .upperCase)
105-
case .snakeCase: KeyConverter.snakeCaseConverter.convert(value: value, variant: .lowerCase)
106-
case .camelSnakeCase: KeyConverter.snakeCaseConverter.convert(value: value, variant: .camelCase)
107-
case .pascalSnakeCase: KeyConverter.snakeCaseConverter.convert(value: value, variant: .pascalCase)
108-
case .screamingSnakeCase: KeyConverter.snakeCaseConverter.convert(value: value, variant: .upperCase)
109-
case .kebabCase: KeyConverter.kebabCaseConverter.convert(value: value, variant: .lowerCase)
110-
case .camelKebabCase: KeyConverter.kebabCaseConverter.convert(value: value, variant: .camelCase)
111-
case .pascalKebabCase: KeyConverter.kebabCaseConverter.convert(value: value, variant: .pascalCase)
112-
case .screamingKebabCase: KeyConverter.kebabCaseConverter.convert(value: value, variant: .upperCase)
113-
case .custom(let converter): converter(value)
115+
case .noChanges: return value
116+
case .custom(let converter): return converter(value)
117+
default:
118+
guard let keyConverter = KeyConverter(keyCase: self), let caseVariant else {
119+
return value
120+
}
121+
return keyConverter.convert(value: value, variant: caseVariant)
114122
}
115123
}
116124
}
@@ -131,7 +139,12 @@ struct KeyConverter: Sendable {
131139
init(separator: String) {
132140
self.separator = separator
133141
}
134-
142+
143+
init?(keyCase: CodingKeyCase) {
144+
guard let separator = keyCase.separator else { return nil }
145+
self.init(separator: separator)
146+
}
147+
135148
func convert(value: String, variant: CaseVariant) -> String {
136149
// Remove any special characters at the beginning/end
137150
let isAllCaps = value.isAllCaps

Sources/CodableWrapperMacros/Convenience/ConvenienceExtensions.swift

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,10 @@ extension MemberBlockItemListSyntax.Element {
2525
decl.as(VariableDeclSyntax.self)?.attributes.matching(matching: T.self) ?? []
2626
}
2727

28-
func attributeSyntax<T: RawRepresentable>(matching rawType: T.Type) -> [(T, AttributeSyntax)] where T.RawValue == String {
29-
decl.as(VariableDeclSyntax.self)?.attributes.matchingSyntax(matching: T.self) ?? []
30-
}
28+
// Not currently used
29+
// func attributeSyntax<T: RawRepresentable>(matching rawType: T.Type) -> [(T, AttributeSyntax)] where T.RawValue == String {
30+
// decl.as(VariableDeclSyntax.self)?.attributes.matchingSyntax(matching: T.self) ?? []
31+
// }
3132

3233
func attributeSyntax(named name: String) -> AttributeSyntax? {
3334
attribute(named: name)?.as(AttributeSyntax.self)
@@ -57,14 +58,15 @@ extension AttributeListSyntax {
5758
}
5859
}
5960

60-
func matchingSyntax<T: RawRepresentable>(matching rawType: T.Type) -> [(T, AttributeSyntax)] where T.RawValue == String {
61-
compactMap {
62-
guard let attributeName = $0.identifierName?.trimmingCharacters(in: .whitespacesAndNewlines), let syntax = $0.as(AttributeSyntax.self), let type = T(rawValue: attributeName) else {
63-
return nil
64-
}
65-
return (type, syntax)
66-
}
67-
}
61+
// Not currently used
62+
// func matchingSyntax<T: RawRepresentable>(matching rawType: T.Type) -> [(T, AttributeSyntax)] where T.RawValue == String {
63+
// compactMap {
64+
// guard let attributeName = $0.identifierName?.trimmingCharacters(in: .whitespacesAndNewlines), let syntax = $0.as(AttributeSyntax.self), let type = T(rawValue: attributeName) else {
65+
// return nil
66+
// }
67+
// return (type, syntax)
68+
// }
69+
// }
6870
}
6971
extension AttributeListSyntax.Element {
7072
var identifierName: String? {

Sources/CodableWrapperMacros/ErrorHandling.swift

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,9 @@ extension AttributeSyntax {
5252
throw DiagnosticsError(diagnostics: [.init(node: self, syntaxError: .codingKeyValueRequired)])
5353
}
5454
let argument = argumentList[argumentList.index(argumentList.startIndex, offsetBy: index)]
55-
guard argument.expression.is(StringLiteralExprSyntax.self) else {
56-
throw DiagnosticsError(diagnostics: [.init(node: self, syntaxError: .mustBeStringLiteral)])
57-
}
58-
// Uses the value in the Macro
55+
// Get the value of the macro
5956
guard let customKeyValue = argument.expression.as(StringLiteralExprSyntax.self) else {
60-
throw DiagnosticsError(diagnostics: [.init(node: self, syntaxError: .codingKeyValueRequired)])
57+
throw DiagnosticsError(diagnostics: [.init(node: self, syntaxError: .mustBeStringLiteral)])
6158
}
6259

6360
return ExprSyntax(customKeyValue)

Sources/CodableWrapperMacros/TypeMacroContainers.swift

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,18 +28,20 @@ struct CodableMacroStructContainer {
2828
}
2929

3030
func attributeNameCanGenerate(name: String?) -> Bool {
31-
switch name {
32-
case .none: false
33-
case CustomCodable.macroName:
34-
true
35-
case CodingKeyPrefix.macroName:
36-
!codableDefined
37-
case CodingKeySuffix.macroName:
38-
attributeNameCanGenerate(name: CodingKeyPrefix.macroName) && codingKeyPrefix == nil
39-
default:
40-
attributeNameCanGenerate(name: CodingKeySuffix.macroName)
41-
&& name == codableAttributes.first?.attributeType.rawValue
42-
}
31+
name == CustomCodable.macroName
32+
// Future work for other attributres being able to generate
33+
// switch name {
34+
// case .none: false
35+
// case CustomCodable.macroName:
36+
// true
37+
// case CodingKeyPrefix.macroName:
38+
// !codableDefined
39+
// case CodingKeySuffix.macroName:
40+
// attributeNameCanGenerate(name: CodingKeyPrefix.macroName) && codingKeyPrefix == nil
41+
// default:
42+
// attributeNameCanGenerate(name: CodingKeySuffix.macroName)
43+
// && name == codableAttributes.first?.attributeType.rawValue
44+
// }
4345
}
4446
}
4547

Sources/CodableWrappers/StaticCoders/BoolCoding.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ public struct BoolAsStringValueProvider: NonConformingBoolValueProvider {
3131
case "true": return true
3232
case "false": return false
3333
default:
34-
print("Failed to convert \(typeValue) to Boolean return nil")
3534
return nil
3635
}
3736
}

Sources/CodableWrappers/StaticCoders/DateCoding.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ extension DateFormatterStaticDecoder {
6363
let stringValue = try String(from: decoder)
6464

6565
guard let value = dateFormatter.date(from: stringValue) else {
66-
let description = "Expected \(Data.self) but could not convert \(stringValue) to Data"
66+
let description = "Expected \(Date.self) but could not convert \(stringValue) to Date"
6767
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath,
6868
debugDescription: description))
6969
}

Sources/CodableWrappers/StaticCoders/FloatingPointCoding.swift

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@ public protocol NonConformingDecimalValueProvider {
1919

2020
/// Uses the `ValueProvider` for (de)serialization of a non-conforming `Float`
2121
public struct NonConformingFloatStaticCoder<ValueProvider: NonConformingDecimalValueProvider>: StaticCoder {
22-
private init() { }
23-
2422
public static func decode(from decoder: Decoder) throws -> Float {
2523
guard let stringValue = try? String(from: decoder) else {
2624
return try Float(from: decoder)
@@ -55,8 +53,6 @@ public struct NonConformingFloatStaticCoder<ValueProvider: NonConformingDecimalV
5553

5654
/// Uses the `ValueProvider` for (de)serialization of a non-conforming `Double`
5755
public struct NonConformingDoubleStaticCoder<ValueProvider: NonConformingDecimalValueProvider>: StaticCoder {
58-
private init() { }
59-
6056
public static func decode(from decoder: Decoder) throws -> Double {
6157
guard let stringValue = try? String(from: decoder) else {
6258
return try Double(from: decoder)

Tests/CodableWrapperMacrosTests/CodingKeyMacroErrorTests.swift

Lines changed: 102 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ final class CodingKeyMacroErrorTests: XCTestCase {
174174
// macros: testMacros)
175175
}
176176

177-
func testThrowsErrorWhenCodingKeyImplemented() throws {
177+
func testThrowsErrorWhenCodingKeysImplemented() throws {
178178
assertMacroExpansion(
179179
"""
180180
@CustomCodable struct TestCodable: Codable {
@@ -215,6 +215,21 @@ final class CodingKeyMacroErrorTests: XCTestCase {
215215
""",
216216
diagnostics: [.init(error: .mustBeStringLiteral, line: 3, column: 5)],
217217
macros: testMacros)
218+
219+
assertMacroExpansion(
220+
"""
221+
@CustomCodable struct TestCodable: Codable {
222+
@CustomCodingKey(1)
223+
let originalKey: String
224+
}
225+
""",
226+
expandedSource: """
227+
struct TestCodable: Codable {
228+
let originalKey: String
229+
}
230+
""",
231+
diagnostics: [.init(error: .mustBeStringLiteral, line: 2, column: 5)],
232+
macros: testMacros)
218233
}
219234

220235
func testThrowsDueToEmptyStringCodingKey() throws {
@@ -234,7 +249,7 @@ final class CodingKeyMacroErrorTests: XCTestCase {
234249
macros: testMacros)
235250
}
236251

237-
func testThrowsDueToNoeCodableMacro() throws {
252+
func testThrowsDueToNoCodableMacro() throws {
238253
assertMacroExpansion(
239254
"""
240255
@SnakeCase struct TestCodable: Codable {
@@ -250,6 +265,21 @@ final class CodingKeyMacroErrorTests: XCTestCase {
250265
diagnostics: [.init(error: .requiresCodableMacro(macroName: "SnakeCase"), line: 1, column: 1)],
251266
macros: testMacros)
252267

268+
assertMacroExpansion(
269+
"""
270+
@CodingKeySuffix struct TestCodable: Codable {
271+
@CustomCodingKey("")
272+
let originalKey: String
273+
}
274+
""",
275+
expandedSource: """
276+
struct TestCodable: Codable {
277+
let originalKey: String
278+
}
279+
""",
280+
diagnostics: [.init(error: .requiresCodableMacro(macroName: "CodingKeySuffix"), line: 1, column: 1)],
281+
macros: testMacros)
282+
253283
assertMacroExpansion(
254284
"""
255285
@CodingKeyPrefix struct TestCodable: Codable {
@@ -265,6 +295,76 @@ final class CodingKeyMacroErrorTests: XCTestCase {
265295
diagnostics: [.init(error: .requiresCodableMacro(macroName: "CodingKeyPrefix"), line: 1, column: 1)],
266296
macros: testMacros)
267297
}
298+
299+
func testThrowsWhenNotStruct() throws {
300+
assertMacroExpansion(
301+
"""
302+
@CustomCodable class TestCodable: Codable {
303+
let originalKey: String
304+
}
305+
""",
306+
expandedSource: """
307+
class TestCodable: Codable {
308+
let originalKey: String
309+
}
310+
""",
311+
diagnostics: [.init(error: .canOnlyBeAttachedToStruct(name: "@CustomCodable"), line: 1, column: 1)],
312+
macros: testMacros)
313+
314+
assertMacroExpansion(
315+
"""
316+
@SnakeCase enum TestCodable: Codable {
317+
case test
318+
}
319+
""",
320+
expandedSource: """
321+
enum TestCodable: Codable {
322+
case test
323+
}
324+
""",
325+
diagnostics: [.init(error: .canOnlyBeAttachedToPropertiesAndStructs(name: "@SnakeCase"), line: 1, column: 1)],
326+
macros: testMacros)
327+
}
328+
329+
func testThrowsWithEmptyCodingKey() throws {
330+
assertMacroExpansion(
331+
"""
332+
@CustomCodable @SnakeCase struct TestCodable: Codable {
333+
@CustomCodingKey()
334+
let originalKey: String
335+
}
336+
""",
337+
expandedSource: """
338+
struct TestCodable: Codable {
339+
let originalKey: String
340+
}
341+
""",
342+
diagnostics: [.init(error: .codingKeyValueRequired, line: 2, column: 5)],
343+
macros: testMacros)
344+
}
345+
346+
func testThrowsWhenAttachedToFunction() throws {
347+
assertMacroExpansion(
348+
"""
349+
@CustomCodable struct TestCodable: Codable {
350+
let originalKey: String
351+
@CustomCodingKey("test")
352+
func test() -> String { "" }
353+
}
354+
""",
355+
expandedSource: """
356+
struct TestCodable: Codable {
357+
let originalKey: String
358+
func test() -> String { "" }
359+
360+
private enum CodingKeys: String, CodingKey {
361+
case originalKey = "originalKey"
362+
}
363+
}
364+
""",
365+
diagnostics: [.init(error: .canOnlyBeAttachedToProperty(name: "@CustomCodingKey"), line: 3, column: 5)],
366+
macros: testMacros)
367+
}
268368
}
269369

270370
#endif

Tests/CodableWrapperMacrosTests/Helpers.swift

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,18 @@ import XCTest
1010
@testable import CodableWrapperMacros
1111

1212
extension DiagnosticSpec {
13-
init(warning: SyntaxWarning, line: Int, column: Int) {
14-
self.init(message: warning.localizedDescription, line: line, column: column, severity: .warning)
13+
init(warning: SyntaxWarning, line: Int, column: Int,
14+
originatorFile: StaticString = #filePath,
15+
originatorLine: UInt = #line) {
16+
self.init(message: warning.localizedDescription, line: line, column: column, severity: .warning,
17+
originatorFile: originatorFile, originatorLine: originatorLine)
1518
}
1619

17-
init(error: SyntaxError, line: Int, column: Int) {
18-
self.init(message: error.localizedDescription, line: line, column: column, severity: .error)
20+
init(error: SyntaxError, line: Int, column: Int,
21+
originatorFile: StaticString = #filePath,
22+
originatorLine: UInt = #line) {
23+
self.init(message: error.localizedDescription, line: line, column: column, severity: .error,
24+
originatorFile: originatorFile, originatorLine: originatorLine)
1925
}
2026
}
2127

Tests/CodableWrapperMacrosTests/KeyConverterTests.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,18 @@ import Nimble
1313
final class KeyConverterTests: QuickSpec {
1414
override class func spec() {
1515
describe("KeyConverter") {
16+
describe("GeneralTests") {
17+
context("CustomCase") {
18+
it("ConvertsCorrectly") {
19+
expect(CodingKeyCase.custom({ $0 + "Test" }).makeKeyValue(from: "Key")) == "KeyTest"
20+
}
21+
}
22+
context("IsNumeric") {
23+
it("ConvertsCorrectly") {
24+
expect(KeyConverter.snakeCaseConverter.convert(value: "12345", variant: .camelCase)) == "12345"
25+
}
26+
}
27+
}
1628
describe("SnakeCase") {
1729
let converter = KeyConverter.snakeCaseConverter
1830
context("WithLowerCase") {
@@ -82,6 +94,13 @@ final class KeyConverterTests: QuickSpec {
8294
}
8395
}
8496
}
97+
describe("Invalid Cases") {
98+
context("WithNoCaseSeparator") {
99+
it("ReturnsNil") {
100+
expect(KeyConverter(keyCase: .custom({ $0 }))) == nil
101+
}
102+
}
103+
}
85104
}
86105
}
87106
}

0 commit comments

Comments
 (0)