diff --git a/DictionaryStorage.podspec b/DictionaryStorage.podspec index 246606d..02e4a5a 100644 --- a/DictionaryStorage.podspec +++ b/DictionaryStorage.podspec @@ -19,7 +19,28 @@ Pod::Spec.new do |s| s.source = { :git => "https://github.com/naan/DictionaryStorage.git", :tag => "#{s.version}" } - s.prepare_command = 'swift build -c release && cp -f .build/release/DictionaryStorageMacros ./Binary' + s.prepare_command = <<-CMD + set -e + swift build -c release + if [ $? -ne 0 ]; then + echo "Error: Failed to build DictionaryStorage" + exit 1 + fi + + if [ -f ".build/release/DictionaryStorageMacros-tool" ]; then + cp -f .build/release/DictionaryStorageMacros-tool ./Binary/DictionaryStorageMacros + elif [ -f ".build/release/DictionaryStorageMacros" ]; then + cp -f .build/release/DictionaryStorageMacros ./Binary/DictionaryStorageMacros + else + echo "Error: Neither DictionaryStorageMacros-tool nor DictionaryStorageMacros found in .build/release" + exit 1 + fi + + if [ $? -ne 0 ]; then + echo "Error: Failed to copy DictionaryStorageMacros to Binary directory" + exit 1 + fi + CMD s.source_files = "Sources/DictionaryStorage/*.swift" s.swift_versions = "5.9" diff --git a/Package.swift b/Package.swift index a3c3aa9..04fd2e7 100644 --- a/Package.swift +++ b/Package.swift @@ -15,7 +15,7 @@ let package = Package( ) ], dependencies: [ - .package(url: "https://github.com/apple/swift-syntax.git", from: "509.0.0"), + .package(url: "https://github.com/apple/swift-syntax.git", from: "510.0.0"), .package(url: "https://github.com/pointfreeco/swift-macro-testing.git", from: "0.3.0") ], targets: [ diff --git a/Sources/DictionaryStorage/DictionaryStorage.swift b/Sources/DictionaryStorage/DictionaryStorage.swift index 4217404..41e3515 100644 --- a/Sources/DictionaryStorage/DictionaryStorage.swift +++ b/Sources/DictionaryStorage/DictionaryStorage.swift @@ -58,9 +58,9 @@ public macro DictionaryStorageProperty(_ key: String) = // MARK: - RawRepresentable /// Automating RawRepresentable Conformance or enum with string. -@attached(member, names: named(rawValue), named(init)) +@attached(member, names: named(rawValue), named(init), named(stringValue)) @attached(extension, conformances: RawRepresentable, Equatable) -public macro StringRawRepresentation() = +public macro StringRawRepresentation(noInit: Bool = false, pureName: Bool = false) = #externalMacro(module: "DictionaryStorageMacros", type: "StringRepresentationMacro") /// Customize name of the key for @StringRawRepresentation. diff --git a/Sources/DictionaryStorageMacros/StringRepresentationMacro.swift b/Sources/DictionaryStorageMacros/StringRepresentationMacro.swift index fc12837..e3e9a02 100644 --- a/Sources/DictionaryStorageMacros/StringRepresentationMacro.swift +++ b/Sources/DictionaryStorageMacros/StringRepresentationMacro.swift @@ -32,7 +32,12 @@ extension StringRepresentationMacro: ExtensionMacro { """ guard let rawRepresentable = rawRepresentableExtension.as(ExtensionDeclSyntax.self) else { return [] } guard let equtable = equatableExtension.as(ExtensionDeclSyntax.self) else { return [] } - + + let noInit = node.getInputParameter("noInit")?.as(BooleanLiteralExprSyntax.self)?.literal.tokenKind == .keyword(.true) + + if noInit { + return [equtable] + } return [rawRepresentable, equtable] } } @@ -58,90 +63,105 @@ extension StringRepresentationMacro: MemberMacro { let modifier: TokenSyntax = declaration.modifiers.isPublic ? "public" : "" - let initializer = try InitializerDeclSyntax("\(modifier) init?(rawValue: String)") { - try SwitchExprSyntax("switch rawValue") { - var hasDefault = false + let noInit = node.getInputParameter("noInit")?.as(BooleanLiteralExprSyntax.self)?.literal.tokenKind == .keyword(.true) + let pureName = node.getInputParameter("pureName")?.as(BooleanLiteralExprSyntax.self)?.literal.tokenKind == .keyword(.true) + + let rawValueVariable = try VariableDeclSyntax("\(modifier) var rawValue: String") { + "stringValue" + } + let stringValueVariable = try VariableDeclSyntax("\(modifier) var stringValue: String") { + try SwitchExprSyntax("switch self") { for caseDecl in cases { let customName = customName(for: caseDecl) - let customPrefix = customPrefix(for: caseDecl) + let customPrefix = customPrefix(for: caseDecl) for element in caseDecl.elements { - let name = customName ?? element.name.trimmed + let value = customName ?? element.name.trimmed - if element.parameterClause == nil { + if element.parameterClause == nil || pureName { SwitchCaseSyntax( """ - case "\(name)": - self = .\(element.name.trimmed) + case .\(element.name.trimmed): + return "\(value)" """ ) } else if let customPrefix { SwitchCaseSyntax( """ - case let name where name.hasPrefix("\(customPrefix)"): - self = .\(name)(String(name.suffix(from: name.index(name.startIndex, offsetBy: "\(customPrefix)".count)))) + case .\(element.name.trimmed)(let value): + return "\(customPrefix)" + value """ ) } else { - let _ = (hasDefault = true) SwitchCaseSyntax( """ - default: - self = .\(name)(rawValue) + case .\(element.name.trimmed)(let value): + return value """ ) } } } - if !hasDefault { - SwitchCaseSyntax( - """ - default: - return nil - """ - ) - } } } + + if noInit { + return [ + DeclSyntax(rawValueVariable), + DeclSyntax(stringValueVariable) + ] + } - let variable = try VariableDeclSyntax("\(modifier) var rawValue: String") { - try SwitchExprSyntax("switch self") { + let initializer = try InitializerDeclSyntax("\(modifier) init?(rawValue: String)") { + try SwitchExprSyntax("switch rawValue") { + var defaultValue: TokenSyntax? for caseDecl in cases { let customName = customName(for: caseDecl) - let customPrefix = customPrefix(for: caseDecl) + let customPrefix = customPrefix(for: caseDecl) for element in caseDecl.elements { - let value = customName ?? element.name.trimmed + let name = customName ?? element.name.trimmed if element.parameterClause == nil { SwitchCaseSyntax( """ - case .\(element.name.trimmed): - return "\(value)" + case "\(name)": + self = .\(element.name.trimmed) """ ) } else if let customPrefix { SwitchCaseSyntax( """ - case .\(element.name.trimmed)(let value): - return "\(customPrefix)" + value + case let name where name.hasPrefix("\(customPrefix)"): + self = .\(name)(String(name.suffix(from: name.index(name.startIndex, offsetBy: "\(customPrefix)".count)))) """ ) } else { - SwitchCaseSyntax( - """ - case .\(element.name.trimmed)(let value): - return value - """ - ) + let _ = (defaultValue = name) } } } + if let name = defaultValue { + SwitchCaseSyntax( + """ + default: + self = .\(name)(rawValue) + """ + ) + } else { + SwitchCaseSyntax( + """ + default: + return nil + """ + ) + } } } return [ - DeclSyntax(variable), + DeclSyntax(rawValueVariable), + DeclSyntax(stringValueVariable), DeclSyntax(initializer) ] } @@ -171,3 +191,15 @@ extension StringRepresentationMacro { return true } } + +extension AttributeSyntax { + /// 获取宏的参数 + func getInputParameter(_ name: String) -> ExprSyntax? { + if case let .argumentList(arguments) = arguments, + let element = arguments.first(where: { $0.label?.text == name }) + { + return element.expression + } + return nil + } +} diff --git a/Tests/DictionaryStorageMacrosTests/StringRepresentationMacroTests.swift b/Tests/DictionaryStorageMacrosTests/StringRepresentationMacroTests.swift index 4b56624..996d59c 100644 --- a/Tests/DictionaryStorageMacrosTests/StringRepresentationMacroTests.swift +++ b/Tests/DictionaryStorageMacrosTests/StringRepresentationMacroTests.swift @@ -38,6 +38,10 @@ final class StringRepresentationMacroTests: XCTestCase { case other(String) var rawValue: String { + stringValue + } + + var stringValue: String { switch self { case .tourist: return "tourist" @@ -94,6 +98,10 @@ final class StringRepresentationMacroTests: XCTestCase { case other(String) var rawValue: String { + stringValue + } + + var stringValue: String { switch self { case .text: return "text" @@ -189,6 +197,10 @@ final class StringRepresentationMacroTests: XCTestCase { case world(String) public var rawValue: String { + stringValue + } + + public var stringValue: String { switch self { case .hello: return "hello" @@ -231,6 +243,10 @@ final class StringRepresentationMacroTests: XCTestCase { case hello public var rawValue: String { + stringValue + } + + public var stringValue: String { switch self { case .hello: return "hello" @@ -263,32 +279,44 @@ final class StringRepresentationMacroTests: XCTestCase { """ @StringRawRepresentation public enum Visa { - case hello, world + case hello_world + case hello_world_2 case custom(String) + case hello_world_3 } """, expandedSource: """ public enum Visa { - case hello, world + case hello_world + case hello_world_2 case custom(String) + case hello_world_3 public var rawValue: String { + stringValue + } + + public var stringValue: String { switch self { - case .hello: - return "hello" - case .world: - return "world" + case .hello_world: + return "hello_world" + case .hello_world_2: + return "hello_world_2" case .custom(let value): return value + case .hello_world_3: + return "hello_world_3" } } public init?(rawValue: String) { switch rawValue { - case "hello": - self = .hello - case "world": - self = .world + case "hello_world": + self = .hello_world + case "hello_world_2": + self = .hello_world_2 + case "hello_world_3": + self = .hello_world_3 default: self = .custom(rawValue) } @@ -325,6 +353,10 @@ final class StringRepresentationMacroTests: XCTestCase { case custom(String) public var rawValue: String { + stringValue + } + + public var stringValue: String { switch self { case .hello: return "hello" diff --git a/Tests/DictionaryStorageTests/StringRepresentationTests.swift b/Tests/DictionaryStorageTests/StringRepresentationTests.swift index 93b33c0..ccd8935 100644 --- a/Tests/DictionaryStorageTests/StringRepresentationTests.swift +++ b/Tests/DictionaryStorageTests/StringRepresentationTests.swift @@ -24,5 +24,16 @@ final class StringRepresentationTests: XCTestCase { XCTAssertEqual(InputType(rawValue: "select-one"), .option) XCTAssertEqual(InputType(rawValue: "password"), .password) + XCTAssertEqual(InputType.email.rawValue, "email") + XCTAssertEqual(InputType.text.rawValue, "text") + XCTAssertEqual(InputType.unknown("custom").rawValue, "custom") + XCTAssertEqual(InputType.option.rawValue, "select-one") + XCTAssertEqual(InputType.password.rawValue, "password") + + XCTAssertEqual(InputType.email.stringValue, "email") + XCTAssertEqual(InputType.text.stringValue, "text") + XCTAssertEqual(InputType.unknown("custom").stringValue, "custom") + XCTAssertEqual(InputType.option.stringValue, "select-one") + XCTAssertEqual(InputType.password.stringValue, "password") } }