From 9b0396d027f3c8ad1d76c02a3a34f558d8697146 Mon Sep 17 00:00:00 2001 From: MahdiBM Date: Thu, 25 Jul 2024 14:04:06 +0330 Subject: [PATCH] some cleanup and tests --- Package.swift | 2 +- Sources/EnumeratorMacro/Enumerator.swift | 139 ++++++++++++++++++ .../Types/EOptionalsArray.swift | 114 -------------- .../EnumeratorMacroTests.swift | 50 ++++--- 4 files changed, 166 insertions(+), 139 deletions(-) delete mode 100644 Sources/EnumeratorMacroImpl/Types/EOptionalsArray.swift diff --git a/Package.swift b/Package.swift index d92d926..587c664 100644 --- a/Package.swift +++ b/Package.swift @@ -21,7 +21,7 @@ let package = Package( dependencies: [ .package( url: "https://github.com/swiftlang/swift-syntax", - from: "510.0.0" + "510.0.0" ..< "601.0.0" ), .package( url: "https://github.com/hummingbird-project/swift-mustache", diff --git a/Sources/EnumeratorMacro/Enumerator.swift b/Sources/EnumeratorMacro/Enumerator.swift index 8fee2ae..88c15a9 100644 --- a/Sources/EnumeratorMacro/Enumerator.swift +++ b/Sources/EnumeratorMacro/Enumerator.swift @@ -6,3 +6,142 @@ public macro Enumerator( module: "EnumeratorMacroImpl", type: "EnumeratorMacroType" ) + +@Enumerator(""" +var caseName: String { + switch self { + {{#cases}} + case .{{name}}: "{{#first(parameters)}} {{name}} {{/first(parameters)}}" + {{/cases}} + } +} +""") +enum TestEnum { + case a(val1: String, val2: Int) + case b + case testCase(testValue: String) +} + +public protocol LocalizationServiceProtocol { + static func localizedString(language: String, term: String, parameters: Any...) -> String +} + +package enum SharedConfiguration { + package enum Env: String { + case local + case testing + case prod + } + + package static var env: Env { fatalError() } +} + +@Enumerator(allowedComments: ["business_error", "l8n_params"], +""" +public enum Subtype: String, Equatable { + {{#cases}} + case {{name}} + {{/cases}} +} +""", +""" +public var subtype: Subtype { + switch self { + {{#cases}} + case .{{name}}: + .{{name}} + {{/cases}} + } +} +""", +""" +public var errorCode: String { + switch self { + {{#cases}} + case .{{name}}: + "ERROR-{{plusOne(index)}}" + {{/cases}} + } +} +""", +""" +public var loggerMetadata: [String: String] { + switch self { + {{#cases}} {{^isEmpty(parameters)}} + case let .{{name}}{{withParens(joined(names(parameters)))}}: + [ + "caseName": self.caseName, + {{#names(parameters)}} + "case_{{.}}": String(reflecting: {{.}}), + {{/names(parameters)}} + ] + {{/isEmpty(parameters)}} {{/cases}} + default: + ["caseName": self.caseName] + } +} +""", +""" +private var localizationParameters: [Any] { + switch self { + {{#cases}} {{^isEmpty(parameters)}} + + {{^isEmpty(l8n_params(comments))}} + case let .{{name}}{{withParens(joined(names(parameters)))}}: + [{{l8n_params(comments)}}] + {{/isEmpty(l8n_params(comments))}} + + {{^exists(l8n_params(comments))}} + case let .{{name}}{{withParens(joined(names(parameters)))}}: + [ + {{#parameters}} + {{name}}{{#isOptional}} as Any{{/isOptional}}, + {{/parameters}} + ] + {{/exists(l8n_params(comments))}} + + {{/isEmpty(parameters)}} {{/cases}} + default: + [] + } +} +""") +@Enumerator( + allowedComments: ["business_error", "l8n_params"], + #""" + package var isBusinessLogicError: Bool { + switch self { + case + {{#cases}}{{#bool(business_error(comments))}} + .{{name}}, + {{/bool(business_error(comments))}}{{/cases}} + : + return true + default: + return false + } + } + """# +) +public enum ErrorMessage { + public static let localizationServiceType: LocalizationServiceProtocol.Type? = nil + + case allergenAlreadyAdded // business_error + case alreadyOngoingInventory + case apiKeyWithoutEnoughPermission(integration: String, other: Bool?, Int) + case databaseError(error: Error, isConstraintViolation: Bool) // business_error; l8n_params: + + public var caseName: String { + self.subtype.rawValue + } + + public func toString(_ language: String) -> String { + let translation = Self.localizationServiceType?.localizedString( + language: language, + term: "api.\(self.caseName)", + parameters: self.localizationParameters + ) + return translation ?? (SharedConfiguration.env == .testing ? String(reflecting: self) : "") + } +} + diff --git a/Sources/EnumeratorMacroImpl/Types/EOptionalsArray.swift b/Sources/EnumeratorMacroImpl/Types/EOptionalsArray.swift deleted file mode 100644 index c194fe2..0000000 --- a/Sources/EnumeratorMacroImpl/Types/EOptionalsArray.swift +++ /dev/null @@ -1,114 +0,0 @@ -import SwiftDiagnostics -import Mustache - -struct EOptionalsArray { - fileprivate let underlying: [EOptional] - - init(underlying: [Element?]) { - self.underlying = underlying.map(EOptional.init) - } - - init(underlying: [EOptional]) { - self.underlying = underlying - } -} - -extension EOptionalsArray: Sequence, MustacheSequence { - func makeIterator() -> Array>.Iterator { - self.underlying.makeIterator() - } -} - -extension EOptionalsArray: CustomStringConvertible { - var description: String { - self.underlying.description - } -} - -extension EOptionalsArray: WithNormalizedTypeName { - static var normalizedTypeName: String { - "[Optional<\(bestEffortTypeName(Element.self))>]" - } -} - -extension EOptionalsArray: CustomReflectable { - var customMirror: Mirror { - Mirror(reflecting: self.underlying) - } -} - -extension EOptionalsArray: EMustacheTransformable { - func transform(_ name: String) -> Any? { - 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 "isEmpty": - return self.underlying.isEmpty - 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": - if Self.self is EOptionalsArray.Type { - RenderingContext.current.context.diagnose( - Diagnostic( - node: RenderingContext.current.node, - message: MacroError.redundantKeyValuesFunctionCall - ) - ) - return self - } - let split: [EKeyValue] = self.underlying - .compactMap { $0.toOptional().map { String(describing: $0) } } - .compactMap(EKeyValue.init(from:)) - return EArray(underlying: split) - default: - if let keyValues = self as? EOptionalsArray { - /// Don't throw even if the key doesn't exist. - switch keyValues.underlying.first(where: { $0.toOptional()?.key.underlying == name }) { - case let .some(wrapped): - return wrapped.map(\.value) - case nil: - return EOptional.none - } - } - if let comparable = self as? EComparableSequence { - /// The underlying type is in charge of adding a diagnostic, if needed. - return comparable.comparableTransform(name) - } - RenderingContext.current.addOrReplaceDiagnostic( - .invalidTransform( - transform: name, - normalizedTypeName: Self.normalizedTypeName - ) - ) - return nil - } - } -} - -extension EOptionalsArray: EComparableSequence where Element: Comparable { - func comparableTransform(_ name: String) -> Any? { - switch name { - case "sorted": - return EOptionalsArray(underlying: self.underlying.sorted()) - default: - RenderingContext.current.addOrReplaceDiagnostic( - .invalidTransform( - transform: name, - normalizedTypeName: Self.normalizedTypeName - ) - ) - return nil - } - } -} diff --git a/Tests/EnumeratorMacroTests/EnumeratorMacroTests.swift b/Tests/EnumeratorMacroTests/EnumeratorMacroTests.swift index 5ae3b89..074b360 100644 --- a/Tests/EnumeratorMacroTests/EnumeratorMacroTests.swift +++ b/Tests/EnumeratorMacroTests/EnumeratorMacroTests.swift @@ -15,28 +15,28 @@ final class EnumeratorMacroTests: XCTestCase { switch self { {{#cases}} case .{{name}}: - "{{name}}" + "{{lowercased(name)}}" {{/cases}} } } """ ) enum TestEnum { - case a - case b + case aBcD + case eFgH } """#, expandedSource: #""" enum TestEnum { - case a - case b + case aBcD + case eFgH var caseName: String { switch self { - case .a: - "a" - case .b: - "b" + case .aBcD: + "abcd" + case .eFgH: + "efgh" } } } @@ -143,7 +143,7 @@ final class EnumeratorMacroTests: XCTestCase { @Enumerator(""" enum Subtype: String { {{#cases}} - case {{name}} + case {{snakeCased(name)}} {{/cases}} } """, @@ -152,7 +152,7 @@ final class EnumeratorMacroTests: XCTestCase { switch self { {{#cases}} case .{{name}}: - .{{name}} + .{{snakeCased(name)}} {{/cases}} } } @@ -172,7 +172,7 @@ final class EnumeratorMacroTests: XCTestCase { enum Subtype: String { case a case b - case testCase + case test_case } var subtype: Subtype { @@ -182,7 +182,7 @@ final class EnumeratorMacroTests: XCTestCase { case .b: .b case .testCase: - .testCase + .test_case } } } @@ -618,7 +618,7 @@ final class EnumeratorMacroTests: XCTestCase { switch self { {{#cases}} case .{{name}}: - "{{name}}" + "{{camelCased(name)}}" @@ -628,22 +628,22 @@ final class EnumeratorMacroTests: XCTestCase { """ ) enum TestEnum { - case a - case b + case my_case + case my_OTHER_case } """#, /// Should not contain those excessive new lines: expandedSource: #""" enum TestEnum { - case a - case b + case my_case + case my_OTHER_case var caseName: String { switch self { - case .a: - "a" - case .b: - "b" + case .my_case: + "myCase" + case .my_OTHER_case: + "myOtherCase" } } } @@ -660,7 +660,7 @@ final class EnumeratorMacroTests: XCTestCase { """ public var constant: String { switch self { - case {{#cases}}.{{name}}, {{/cases}}: + case {{#sorted(cases)}}.{{name}}, {{/sorted(cases)}}: "some constant" } } @@ -668,6 +668,7 @@ final class EnumeratorMacroTests: XCTestCase { ) enum TestEnum { case a + case c case b } """#, @@ -677,11 +678,12 @@ final class EnumeratorMacroTests: XCTestCase { expandedSource: #""" enum TestEnum { case a + case c case b public var constant: String { switch self { - case .a, .b: + case .a, .b, .c: "some constant" } }