From 9fc7b1c0573dda410fa0f2fc5dff905d37f803cf Mon Sep 17 00:00:00 2001 From: MahdiBM Date: Wed, 17 Jul 2024 19:47:19 +0330 Subject: [PATCH] only use custom types to have full control over transforms --- README.md | 6 +- .../RenderingContext.swift | 4 - .../EnumeratorMacroImpl/Types/EArray.swift | 91 +++++++++--------- Sources/EnumeratorMacroImpl/Types/EBool.swift | 33 +++++++ Sources/EnumeratorMacroImpl/Types/ECase.swift | 10 ++ .../EnumeratorMacroImpl/Types/ECases.swift | 35 ++++--- .../EnumeratorMacroImpl/Types/EKeyValue.swift | 12 ++- .../Types/EMustacheTransformable.swift | 4 + .../EnumeratorMacroImpl/Types/EOptional.swift | 36 ++++--- .../Types/EOptionalsArray.swift | 85 +++++++++-------- .../Types/EParameter.swift | 22 +++-- .../Types/EParameters.swift | 76 +++++++-------- .../EnumeratorMacroImpl/Types/EString.swift | 93 ++++++++++--------- Sources/EnumeratorMacroImpl/Types/Utils.swift | 80 ---------------- .../ConvertToCustomTypesTests.swift | 83 ----------------- .../EParameterTests.swift | 20 ++-- .../EnumeratorMacroTests.swift | 6 +- 17 files changed, 305 insertions(+), 391 deletions(-) create mode 100644 Sources/EnumeratorMacroImpl/Types/EBool.swift create mode 100644 Sources/EnumeratorMacroImpl/Types/EMustacheTransformable.swift delete mode 100644 Sources/EnumeratorMacroImpl/Types/Utils.swift delete mode 100644 Tests/EnumeratorMacroTests/ConvertToCustomTypesTests.swift diff --git a/README.md b/README.md index f1066f8..985dcad 100644 --- a/README.md +++ b/README.md @@ -129,7 +129,7 @@ enum TestEnum { ```swift @Enumerator(""" {{#cases}} -var is{{firstCapitalized(name)}}: Bool { +var is{{capitalized(name)}}: Bool { switch self { case .{{name}}: return true default: return false @@ -219,7 +219,7 @@ enum TestEnum { @Enumerator(""" {{#cases}} {{^empty(parameters)}} -func get{{firstCapitalized(name)}}() -> ({{joined(tupleValue(parameters))}})? { +func get{{capitalized(name)}}() -> ({{joined(tupleValue(parameters))}})? { switch self { case let .{{name}}{{withParens(joined(names(parameters)))}}: return {{withParens(joined(names(parameters)))}} @@ -290,7 +290,7 @@ Although not visible when writing templates, each underlying value that is passe In addition to [`swift-mustache`'s own "functions"/"transforms"](https://docs.hummingbird.codes/2.0/documentation/hummingbird/transforms/), `EnumeratorMacro` supports these transformations for each type: * `String`: - * `firstCapitalized`: Capitalizes the first letter. + * `capitalized`: Capitalizes the first letter. * `snakeCased`: Converts the string from camelCase to snake_case. * `camelCased`: Converts the string from snake_case to camelCase. * `withParens`: If the string is not empty, surrounds it in parenthesis. diff --git a/Sources/EnumeratorMacroImpl/RenderingContext.swift b/Sources/EnumeratorMacroImpl/RenderingContext.swift index 4bf4919..5f0576d 100644 --- a/Sources/EnumeratorMacroImpl/RenderingContext.swift +++ b/Sources/EnumeratorMacroImpl/RenderingContext.swift @@ -4,10 +4,6 @@ final class RenderingContext: @unchecked Sendable { var diagnostic: MacroError? - func cleanDiagnostic() { - self.diagnostic = nil - } - func addOrReplaceDiagnostic(_ error: MacroError) { self.diagnostic = error } diff --git a/Sources/EnumeratorMacroImpl/Types/EArray.swift b/Sources/EnumeratorMacroImpl/Types/EArray.swift index 12153c9..251ab32 100644 --- a/Sources/EnumeratorMacroImpl/Types/EArray.swift +++ b/Sources/EnumeratorMacroImpl/Types/EArray.swift @@ -1,7 +1,7 @@ import Mustache import Foundation -struct EArray { +struct EArray { let underlying: [Element] init(underlying: [Element]) { @@ -38,51 +38,58 @@ extension EArray: CustomReflectable { } } -extension EArray: MustacheTransformable { +extension EArray: EMustacheTransformable { func transform(_ name: String) -> Any? { - if let defaultTransformed = self.underlying.transform(name) { - return convertToCustomTypesIfPossible(defaultTransformed) - } else { - RenderingContext.current.cleanDiagnostic() - switch name { - case "joined": - let joined = self.underlying - .map { String(describing: $0) } - .joined(separator: ", ") - let string = EString(joined) - return string - case "keyValues": - let split: [EKeyValue] = self.underlying - .map { String(describing: $0) } - .compactMap { string -> EKeyValue? in - let split = string.split( - separator: ":", - maxSplits: 1 - ).map { - $0.trimmingCharacters(in: .whitespacesAndNewlines) - } - guard split.count > 0 else { - return nil - } - return EKeyValue( - key: EString(split[0]), - value: EString(split.count > 1 ? split[1] : "") - ) + switch name { + case "first": + return self.underlying.first + case "last": + return self.underlying.last + case "reversed": + return EOptionalsArray(underlying: self.reversed().map { $0 }) + case "count": + return self.underlying.count + case "empty": + return self.underlying.isEmpty + case "sorted": + return EArray(underlying: self.underlying.sorted()) + case "joined": + let joined = self.underlying + .map { String(describing: $0) } + .joined(separator: ", ") + let string = EString(joined) + return string + case "keyValues": + let split: [EKeyValue] = self.underlying + .map { String(describing: $0) } + .compactMap { string -> EKeyValue? in + let split = string.split( + separator: ":", + maxSplits: 1 + ).map { + $0.trimmingCharacters(in: .whitespacesAndNewlines) } - return EArray(underlying: split) - default: - if let keyValues = self as? EArray { - let value = keyValues.first(where: { $0.key == name })?.value - return EOptional(value) - } - RenderingContext.current.addOrReplaceDiagnostic( - .invalidTransform( - transform: name, - normalizedTypeName: Self.normalizedTypeName + guard split.count > 0 else { + return nil + } + return EKeyValue( + key: EString(split[0]), + value: EString(split.count > 1 ? split[1] : "") ) - ) - return nil + } + return EArray(underlying: split) + default: + if let keyValues = self as? EArray { + let value = keyValues.first(where: { $0.key == name })?.value + return EOptional(value) } + RenderingContext.current.addOrReplaceDiagnostic( + .invalidTransform( + transform: name, + normalizedTypeName: Self.normalizedTypeName + ) + ) + return nil } } } diff --git a/Sources/EnumeratorMacroImpl/Types/EBool.swift b/Sources/EnumeratorMacroImpl/Types/EBool.swift new file mode 100644 index 0000000..28c00e1 --- /dev/null +++ b/Sources/EnumeratorMacroImpl/Types/EBool.swift @@ -0,0 +1,33 @@ +import Mustache + +struct EBool { + var underlying: Bool + + init(_ underlying: Bool) { + self.underlying = underlying + } +} + +extension EBool: CustomStringConvertible { + var description: String { + self.underlying.description + } +} + +extension EBool: WithNormalizedTypeName { + static var normalizedTypeName: String { + "Bool" + } +} + +extension EBool: CustomReflectable { + var customMirror: Mirror { + self.underlying.customMirror + } +} + +extension EBool: Comparable { + static func < (lhs: EBool, rhs: EBool) -> Bool { + !lhs.underlying && rhs.underlying + } +} diff --git a/Sources/EnumeratorMacroImpl/Types/ECase.swift b/Sources/EnumeratorMacroImpl/Types/ECase.swift index de63930..9a05e39 100644 --- a/Sources/EnumeratorMacroImpl/Types/ECase.swift +++ b/Sources/EnumeratorMacroImpl/Types/ECase.swift @@ -30,3 +30,13 @@ extension ECase: WithNormalizedTypeName { "Case" } } + +extension ECase: Comparable { + static func < (lhs: ECase, rhs: ECase) -> Bool { + lhs.name < rhs.name + } + + static func == (lhs: ECase, rhs: ECase) -> Bool { + lhs.name == rhs.name + } +} diff --git a/Sources/EnumeratorMacroImpl/Types/ECases.swift b/Sources/EnumeratorMacroImpl/Types/ECases.swift index 4c24e50..a8f33c1 100644 --- a/Sources/EnumeratorMacroImpl/Types/ECases.swift +++ b/Sources/EnumeratorMacroImpl/Types/ECases.swift @@ -18,6 +18,7 @@ extension ECases: CustomStringConvertible { self.underlying.description } } + extension ECases: WithNormalizedTypeName { static var normalizedTypeName: String { "[Case]" @@ -36,26 +37,24 @@ extension ECases: CustomReflectable { } } -extension ECases: MustacheTransformable { +extension ECases: EMustacheTransformable { func transform(_ name: String) -> Any? { - if let defaultTransformed = self.underlying.transform(name) { - return convertToCustomTypesIfPossible(defaultTransformed) - } else { - RenderingContext.current.cleanDiagnostic() - switch name { - case "filterNoParams": - return self.filter(\.parameters.underlying.underlying.isEmpty) - case "filterWithParams": - return self.filter({ !$0.parameters.underlying.underlying.isEmpty }) - default: - RenderingContext.current.addOrReplaceDiagnostic( - .invalidTransform( - transform: name, - normalizedTypeName: Self.normalizedTypeName - ) - ) - return nil + switch name { + case "filterNoParams": + return self.filter(\.parameters.underlying.underlying.isEmpty) + case "filterWithParams": + return self.filter({ !$0.parameters.underlying.underlying.isEmpty }) + default: + if let transformed = self.underlying.transform(name) { + return transformed } + RenderingContext.current.addOrReplaceDiagnostic( + .invalidTransform( + transform: name, + normalizedTypeName: Self.normalizedTypeName + ) + ) + return nil } } } diff --git a/Sources/EnumeratorMacroImpl/Types/EKeyValue.swift b/Sources/EnumeratorMacroImpl/Types/EKeyValue.swift index 06efe20..4a4a1a4 100644 --- a/Sources/EnumeratorMacroImpl/Types/EKeyValue.swift +++ b/Sources/EnumeratorMacroImpl/Types/EKeyValue.swift @@ -22,7 +22,17 @@ extension EKeyValue: WithNormalizedTypeName { } } -extension EKeyValue: MustacheTransformable { +extension EKeyValue: Comparable { + static func < (lhs: EKeyValue, rhs: EKeyValue) -> Bool { + lhs.key < rhs.key + } + + static func == (lhs: EKeyValue, rhs: EKeyValue) -> Bool { + lhs.key == rhs.key + } +} + +extension EKeyValue: EMustacheTransformable { func transform(_ name: String) -> Any? { switch name { case "key": diff --git a/Sources/EnumeratorMacroImpl/Types/EMustacheTransformable.swift b/Sources/EnumeratorMacroImpl/Types/EMustacheTransformable.swift new file mode 100644 index 0000000..7c59430 --- /dev/null +++ b/Sources/EnumeratorMacroImpl/Types/EMustacheTransformable.swift @@ -0,0 +1,4 @@ +import Mustache + +/// The same as `MustacheTransformable` but only for this library's types. +protocol EMustacheTransformable: MustacheTransformable {} diff --git a/Sources/EnumeratorMacroImpl/Types/EOptional.swift b/Sources/EnumeratorMacroImpl/Types/EOptional.swift index 65157ec..cac6140 100644 --- a/Sources/EnumeratorMacroImpl/Types/EOptional.swift +++ b/Sources/EnumeratorMacroImpl/Types/EOptional.swift @@ -1,6 +1,6 @@ import Mustache -enum EOptional { +enum EOptional { case none case some(Wrapped) @@ -75,11 +75,28 @@ extension EOptional: WithNormalizedTypeName { } } -extension EOptional: MustacheTransformable { +extension EOptional: Comparable { + static func < (lhs: EOptional, rhs: EOptional) -> Bool { + switch (lhs, rhs) { + case let (.some(lhs), .some(rhs)): + return lhs < rhs + case (.some, .none): + return false + case (.none, .some): + return true + case (.none, .none): + return false + } + } +} + +extension EOptional: EMustacheTransformable { func transform(_ name: String) -> Any? { switch self { case .none: switch name { + case "empty": + return true case "bool": return false case "exists": @@ -98,18 +115,9 @@ extension EOptional: MustacheTransformable { case "exists": return true default: - if let value = value as? MustacheTransformable { - if let transformed = value.transform(name) { - return transformed - } else { - RenderingContext.current.addOrReplaceDiagnostic( - .invalidTransform( - transform: name, - normalizedTypeName: bestEffortTypeName(Wrapped.self) - ) - ) - return nil - } + if let value = value as? EMustacheTransformable { + /// The underlying type is in charge of adding a diagnostic, if needed. + return value.transform(name) } else { RenderingContext.current.addOrReplaceDiagnostic( .invalidTransform( diff --git a/Sources/EnumeratorMacroImpl/Types/EOptionalsArray.swift b/Sources/EnumeratorMacroImpl/Types/EOptionalsArray.swift index d0ef2e1..a729692 100644 --- a/Sources/EnumeratorMacroImpl/Types/EOptionalsArray.swift +++ b/Sources/EnumeratorMacroImpl/Types/EOptionalsArray.swift @@ -1,6 +1,6 @@ import Mustache -struct EOptionalsArray { +struct EOptionalsArray { fileprivate let underlying: [EOptional] init(underlying: [Element?]) { @@ -41,48 +41,55 @@ extension EOptionalsArray: CustomReflectable { } } -extension EOptionalsArray: MustacheTransformable { +extension EOptionalsArray: EMustacheTransformable { func transform(_ name: String) -> Any? { - if let defaultTransformed = self.underlying.transform(name) { - return convertToCustomTypesIfPossible(defaultTransformed) - } else { - RenderingContext.current.cleanDiagnostic() - switch name { - case "joined": - let joined = self.underlying - .enumerated() - .map { $1.map { String(describing: $0) } ?? "param\($0 + 1)" } - .joined(separator: ", ") - let string = EString(joined) - return string - case "keyValues": - let split: [EKeyValue] = self.underlying - .compactMap { $0.toOptional().map { String(describing: $0) } } - .compactMap { string -> EKeyValue? in - let split = string.split( - separator: ":", - maxSplits: 1 - ).map { - $0.trimmingCharacters(in: .whitespacesAndNewlines) - } - guard split.count == 2 else { - return nil - } - return EKeyValue( - key: EString(split[0]), - value: EString(split[1]) - ) + switch name { + case "first": + return self.underlying.first + case "last": + return self.underlying.last + case "reversed": + return EOptionalsArray(underlying: self.reversed().map { $0 }) + case "count": + return self.underlying.count + case "empty": + return self.underlying.isEmpty + case "sorted": + return EOptionalsArray(underlying: self.underlying.sorted()) + case "joined": + let joined = self.underlying + .enumerated() + .map { $1.map { String(describing: $0) } ?? "param\($0 + 1)" } + .joined(separator: ", ") + let string = EString(joined) + return string + case "keyValues": + let split: [EKeyValue] = self.underlying + .compactMap { $0.toOptional().map { String(describing: $0) } } + .compactMap { string -> EKeyValue? in + let split = string.split( + separator: ":", + maxSplits: 1 + ).map { + $0.trimmingCharacters(in: .whitespacesAndNewlines) } - return EArray(underlying: split) - default: - RenderingContext.current.addOrReplaceDiagnostic( - .invalidTransform( - transform: name, - normalizedTypeName: Self.normalizedTypeName + guard split.count == 2 else { + return nil + } + return EKeyValue( + key: EString(split[0]), + value: EString(split[1]) ) + } + return EArray(underlying: split) + default: + RenderingContext.current.addOrReplaceDiagnostic( + .invalidTransform( + transform: name, + normalizedTypeName: Self.normalizedTypeName ) - return nil - } + ) + return nil } } } diff --git a/Sources/EnumeratorMacroImpl/Types/EParameter.swift b/Sources/EnumeratorMacroImpl/Types/EParameter.swift index 9b3010c..a6a3499 100644 --- a/Sources/EnumeratorMacroImpl/Types/EParameter.swift +++ b/Sources/EnumeratorMacroImpl/Types/EParameter.swift @@ -1,21 +1,21 @@ import SwiftSyntax struct EParameter { - let name: EString? + let name: EOptional let type: EString - let isOptional: Bool + let isOptional: EBool init(parameter: EnumCaseParameterSyntax) { let parameterName = parameter.secondName ?? parameter.firstName - self.name = parameterName.map { .init($0.trimmedDescription) } + self.name = .init(parameterName.map { .init($0.trimmedDescription) }) self.type = .init(parameter.type.trimmedDescription) - self.isOptional = parameter.type.isOptional + self.isOptional = EBool(parameter.type.isOptional) } init(name: EString?, type: EString, isOptional: Bool = false) { - self.name = name + self.name = .init(name) self.type = type - self.isOptional = isOptional + self.isOptional = EBool(isOptional) } } @@ -25,6 +25,16 @@ extension EParameter: WithNormalizedTypeName { } } +extension EParameter: Comparable { + static func < (lhs: EParameter, rhs: EParameter) -> Bool { + lhs.name < rhs.name + } + + static func == (lhs: EParameter, rhs: EParameter) -> Bool { + lhs.name == rhs.name + } +} + private extension TypeSyntax { var isOptional: Bool { switch self.kind { diff --git a/Sources/EnumeratorMacroImpl/Types/EParameters.swift b/Sources/EnumeratorMacroImpl/Types/EParameters.swift index 1191b05..df7c37b 100644 --- a/Sources/EnumeratorMacroImpl/Types/EParameters.swift +++ b/Sources/EnumeratorMacroImpl/Types/EParameters.swift @@ -32,30 +32,37 @@ extension EParameters: CustomReflectable { } } -extension EParameters: MustacheTransformable { +extension EParameters: EMustacheTransformable { func transform(_ name: String) -> Any? { - if let defaultTransformed = self.underlying.transform(name) { - return convertToCustomTypesIfPossible(defaultTransformed) - } else { - RenderingContext.current.cleanDiagnostic() - switch name { - case "names": - let names = self - .enumerated() - .map { idx, element in - element.name ?? "param\(idx + 1)" - } - let array = EArray(underlying: names) - return array - case "types": - let types = self.map(\.type) - let array = EArray(underlying: types) - return array - case "isOptionals": - let types = self.map(\.isOptional) - let array = EArray(underlying: types) - return array - case "namesAndTypes": + switch name { + case "names": + let names = self + .enumerated() + .map { idx, element in + element.name ?? "param\(idx + 1)" + } + let array = EArray(underlying: names) + return array + case "types": + let types = self.map(\.type) + let array = EArray(underlying: types) + return array + case "isOptionals": + let types = self.map(\.isOptional) + let array = EArray(underlying: types) + return array + case "namesAndTypes": + let namesAndTypes = self + .enumerated() + .map { idx, element in + (element.name ?? "param\(idx + 1)") + ": " + element.type + } + let array = EArray(underlying: namesAndTypes) + return array + case "tupleValue": + if self.underlying.underlying.count == 1 { + return EArray(underlying: [underlying.underlying[0].type]) + } else { let namesAndTypes = self .enumerated() .map { idx, element in @@ -63,27 +70,10 @@ extension EParameters: MustacheTransformable { } let array = EArray(underlying: namesAndTypes) return array - case "tupleValue": - if self.underlying.underlying.count == 1 { - return EArray(underlying: [underlying.underlying[0].type]) - } else { - let namesAndTypes = self - .enumerated() - .map { idx, element in - (element.name ?? "param\(idx + 1)") + ": " + element.type - } - let array = EArray(underlying: namesAndTypes) - return array - } - default: - RenderingContext.current.addOrReplaceDiagnostic( - .invalidTransform( - transform: name, - normalizedTypeName: Self.normalizedTypeName - ) - ) - return nil } + default: + /// The underlying type is in charge of adding a diagnostic, if needed. + return self.underlying.transform(name) } } } diff --git a/Sources/EnumeratorMacroImpl/Types/EString.swift b/Sources/EnumeratorMacroImpl/Types/EString.swift index b9904db..749e301 100644 --- a/Sources/EnumeratorMacroImpl/Types/EString.swift +++ b/Sources/EnumeratorMacroImpl/Types/EString.swift @@ -26,55 +26,58 @@ extension EString: CustomReflectable { } } -extension EString: MustacheTransformable { +extension EString: EMustacheTransformable { func transform(_ name: String) -> Any? { - if let defaultTransformed = self.underlying.transform(name) { - return convertToCustomTypesIfPossible(defaultTransformed) - } else { - RenderingContext.current.cleanDiagnostic() - switch name { - case "firstCapitalized": - if self.isEmpty || self[self.startIndex].isUppercase { - return self - } - let modified = self[self.startIndex].uppercased() + self[self.index(after: self.startIndex)...] - return EString(modified) - case "snakeCased": - return self.convertedToSnakeCase() - case "camelCased": - return self.convertToCamelCase() - case "withParens": - return self.isEmpty ? self : "(\(self))" - case "bool": - switch self.lowercased() { - case "true", "1", "yes", "y", "on", "": - return true - default: - return false - } - case "keyValue": - let split = self.split( - separator: ":", - maxSplits: 1 - ).map { - $0.trimmingCharacters(in: .whitespacesAndNewlines) - } - guard split.count > 0 else { - return nil - } - return EKeyValue( - key: EString(split[0]), - value: EString(split.count > 1 ? split[1] : "") - ) + switch name { + case "empty": + return self.isEmpty + case "capitalized": + if self.isEmpty || self[self.startIndex].isUppercase { + return self + } + let modified = self[self.startIndex].uppercased() + self[self.index(after: self.startIndex)...] + return EString(modified) + case "lowercased": + return EString(self.lowercased()) + case "uppercased": + return EString(self.uppercased()) + case "reversed": + return EString(self.reversed()) + case "snakeCased": + return self.convertedToSnakeCase() + case "camelCased": + return self.convertToCamelCase() + case "withParens": + return self.isEmpty ? self : "(\(self))" + case "bool": + switch self.lowercased() { + case "true", "1", "yes", "y", "on", "": + return true default: - RenderingContext.current.addOrReplaceDiagnostic( - .invalidTransform( - transform: name, - normalizedTypeName: Self.normalizedTypeName - ) - ) + return false + } + case "keyValue": + let split = self.split( + separator: ":", + maxSplits: 1 + ).map { + $0.trimmingCharacters(in: .whitespacesAndNewlines) + } + guard split.count > 0 else { return nil } + return EKeyValue( + key: EString(split[0]), + value: EString(split.count > 1 ? split[1] : "") + ) + default: + RenderingContext.current.addOrReplaceDiagnostic( + .invalidTransform( + transform: name, + normalizedTypeName: Self.normalizedTypeName + ) + ) + return nil } } } diff --git a/Sources/EnumeratorMacroImpl/Types/Utils.swift b/Sources/EnumeratorMacroImpl/Types/Utils.swift deleted file mode 100644 index b5f5721..0000000 --- a/Sources/EnumeratorMacroImpl/Types/Utils.swift +++ /dev/null @@ -1,80 +0,0 @@ -func convertToCustomTypesIfPossible(_ value: Any) -> Any { - switch value { - case let `optional` as OptionalProtocol: - return `optional`.asConvertedOptionalAny() - case let string as any StringProtocol: - return EString(string.description) - case let seq as any Sequence: - return EParameters(underlying: seq.map { $0 }) - case let seq as any Sequence: - switch convertHomogeneousArrayToCustomTypes(seq.map { $0 }) { - case let .anys(values): - return EArray(underlying: values) - case let .optionalAnys(values): - return EOptionalsArray(underlying: values) - } - default: - return value - } -} - -enum Elements { - case anys([Any]) - case optionalAnys([EOptional]) -} - -private func convertHomogeneousArrayToCustomTypes(_ values: [Any]) -> Elements { - guard let first = values.first else { - return .anys(values) - } - switch first { - case is any OptionalProtocol: - return .optionalAnys(values.map { - let optionalProtocol = $0 as! (any OptionalProtocol) - let optional = optionalProtocol.asConvertedOptionalAny() - return optional - }) - case is any StringProtocol: - return .anys(values.map { - let string = $0 as! (any StringProtocol) - return EString(string.description) - }) - case is any Sequence: - return .anys(values.map { - let seq = $0 as! any Sequence - return EParameters(underlying: seq.map { $0 }) - }) - case is any Sequence: - return .anys(values.map { - let seq = $0 as! (any Sequence) - switch convertHomogeneousArrayToCustomTypes(seq.map { $0 }) { - case let .anys(values): - return EArray(underlying: values) - case let .optionalAnys(values): - return EOptionalsArray(underlying: values) - } - }) - default: - return .anys(values) - } -} - -private protocol OptionalProtocol { - func asConvertedOptionalAny() -> EOptional -} - -extension Optional: OptionalProtocol { - func asConvertedOptionalAny() -> EOptional { - EOptional(self).map { - convertToCustomTypesIfPossible($0) - } - } -} - -extension EOptional: OptionalProtocol { - func asConvertedOptionalAny() -> EOptional { - self.map { - convertToCustomTypesIfPossible($0) - } - } -} diff --git a/Tests/EnumeratorMacroTests/ConvertToCustomTypesTests.swift b/Tests/EnumeratorMacroTests/ConvertToCustomTypesTests.swift deleted file mode 100644 index edc4fee..0000000 --- a/Tests/EnumeratorMacroTests/ConvertToCustomTypesTests.swift +++ /dev/null @@ -1,83 +0,0 @@ -@testable import EnumeratorMacroImpl -import XCTest - -final class ConvertToCustomTypesTests: XCTestCase { - func testConvertsToEString() { - let value = "" - let converted = convertToCustomTypesIfPossible(value) - let convertedType = type(of: converted) - XCTAssertTrue(convertedType is EString.Type, "\(converted); \(convertedType)") - } - - func testConvertsToEParameters() { - let value: [EParameter] = [.init(name: nil, type: "")] - let converted = convertToCustomTypesIfPossible(value) - let convertedType = type(of: converted) - XCTAssertTrue(convertedType is EParameters.Type, "\(converted); \(convertedType)") - } - - func testConvertsToEOptional() { - let value: String? = nil - let converted = convertToCustomTypesIfPossible(value as Any) - let convertedType = type(of: converted) - XCTAssertTrue(convertedType is EOptional.Type, "\(converted); \(convertedType)") - } - - func testConvertsToEArray() throws { - let value: [Int] = [1] - let converted = convertToCustomTypesIfPossible(value) - let convertedType = type(of: converted) - XCTAssertTrue(convertedType is EArray.Type, "\(converted); \(convertedType)") - - let array = try XCTUnwrap(converted as? EArray) - var iterator = array.makeIterator() - let element = try XCTUnwrap(iterator.next()) - let elementType = type(of: element) - XCTAssertTrue(elementType is Int.Type, "\(element); \(elementType)") - } - - func testConvertsToEArrayAndRecursesForElements() throws { - let value: [String] = [""] - let converted = convertToCustomTypesIfPossible(value) - let convertedType = type(of: converted) - XCTAssertTrue(convertedType is EArray.Type, "\(converted); \(convertedType)") - - let array = try XCTUnwrap(converted as? EArray) - var iterator = array.makeIterator() - let element = try XCTUnwrap(iterator.next()) - let elementType = type(of: element) - XCTAssertTrue(elementType is EString.Type, "\(element); \(elementType)") - } - - func testConvertsToEOptionalsArrayAndRecursesForElements() throws { - let value: [String?] = [""] - let converted = convertToCustomTypesIfPossible(value) - let convertedType = type(of: converted) - XCTAssertTrue(convertedType is EOptionalsArray.Type, "\(converted); \(convertedType)") - - let array = try XCTUnwrap(converted as? EOptionalsArray) - var iterator = array.makeIterator() - let element = try XCTUnwrap(iterator.next()) - let unwrappedElement = try XCTUnwrap(element.toOptional()) - let unwrappedElementType = type(of: unwrappedElement) - XCTAssertTrue(unwrappedElementType is EString.Type, "\(unwrappedElement); \(unwrappedElementType)") - } - - func testConvertsToEOptionalsArrayAndRecursesForElementsWithDifferentValues() throws { - let value: [String?] = [nil, ""] - let converted = convertToCustomTypesIfPossible(value) - let convertedType = type(of: converted) - XCTAssertTrue(convertedType is EOptionalsArray.Type, "\(converted); \(convertedType)") - - let array = try XCTUnwrap(converted as? EOptionalsArray) - var iterator = array.makeIterator() - - let element1 = try XCTUnwrap(iterator.next()) - XCTAssertNil(element1.toOptional()) - - let element2 = try XCTUnwrap(iterator.next()) - let unwrappedElement2 = try XCTUnwrap(element2.toOptional()) - let unwrappedElement2Type = type(of: unwrappedElement2) - XCTAssertTrue(unwrappedElement2Type is EString.Type, "\(unwrappedElement2); \(unwrappedElement2Type)") - } -} diff --git a/Tests/EnumeratorMacroTests/EParameterTests.swift b/Tests/EnumeratorMacroTests/EParameterTests.swift index 88e8c24..9a0f033 100644 --- a/Tests/EnumeratorMacroTests/EParameterTests.swift +++ b/Tests/EnumeratorMacroTests/EParameterTests.swift @@ -14,9 +14,9 @@ final class EParameterTests: XCTestCase { type: type ) - XCTAssertEqual(parameter.name?.underlying, name) + XCTAssertEqual(parameter.name.toOptional()?.underlying, name) XCTAssertEqual(parameter.type.underlying, type) - XCTAssertEqual(parameter.isOptional, false) + XCTAssertEqual(parameter.isOptional.underlying, false) } func testParameterWithSecondName() throws { @@ -29,9 +29,9 @@ final class EParameterTests: XCTestCase { type: type ) - XCTAssertEqual(parameter.name?.underlying, secondName) + XCTAssertEqual(parameter.name.toOptional()?.underlying, secondName) XCTAssertEqual(parameter.type.underlying, type) - XCTAssertEqual(parameter.isOptional, false) + XCTAssertEqual(parameter.isOptional.underlying, false) } func testParameterWithOptionalType() throws { @@ -43,9 +43,9 @@ final class EParameterTests: XCTestCase { type: type ) - XCTAssertEqual(parameter.name?.underlying, name) + XCTAssertEqual(parameter.name.toOptional()?.underlying, name) XCTAssertEqual(parameter.type.underlying, type) - XCTAssertEqual(parameter.isOptional, true) + XCTAssertEqual(parameter.isOptional.underlying, true) } func testParameterWithImplicitlyOptionalType() throws { @@ -57,9 +57,9 @@ final class EParameterTests: XCTestCase { type: type ) - XCTAssertEqual(parameter.name?.underlying, name) + XCTAssertEqual(parameter.name.toOptional()?.underlying, name) XCTAssertEqual(parameter.type.underlying, type) - XCTAssertEqual(parameter.isOptional, true) + XCTAssertEqual(parameter.isOptional.underlying, true) } func testParameterWithSpelledOutOptionalType() throws { @@ -71,9 +71,9 @@ final class EParameterTests: XCTestCase { type: type ) - XCTAssertEqual(parameter.name?.underlying, name) + XCTAssertEqual(parameter.name.toOptional()?.underlying, name) XCTAssertEqual(parameter.type.underlying, type) - XCTAssertEqual(parameter.isOptional, true) + XCTAssertEqual(parameter.isOptional.underlying, true) } private func makeParameter( diff --git a/Tests/EnumeratorMacroTests/EnumeratorMacroTests.swift b/Tests/EnumeratorMacroTests/EnumeratorMacroTests.swift index 9e0c9dd..7df53a6 100644 --- a/Tests/EnumeratorMacroTests/EnumeratorMacroTests.swift +++ b/Tests/EnumeratorMacroTests/EnumeratorMacroTests.swift @@ -82,7 +82,7 @@ final class EnumeratorMacroTests: XCTestCase { #""" @Enumerator(""" {{#cases}} - var is{{firstCapitalized(name)}}: Bool { + var is{{capitalized(name)}}: Bool { switch self { case .{{name}}: return true @@ -196,7 +196,7 @@ final class EnumeratorMacroTests: XCTestCase { @Enumerator(""" {{#cases}} {{^empty(parameters)}} - func get{{firstCapitalized(name)}}() -> ({{joined(tupleValue(parameters))}})? { + func get{{capitalized(name)}}() -> ({{joined(tupleValue(parameters))}})? { switch self { case let .{{name}}{{withParens(joined(names(parameters)))}}: return {{withParens(joined(names(parameters)))}} @@ -786,7 +786,7 @@ final class EnumeratorMacroTests: XCTestCase { @Enumerator(""" {{#cases}} -var is{{firstCapitalized(name)}}: Bool { +var is{{capitalized(name)}}: Bool { switch self { case .{{name}}: return true