Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion DictionaryStorage.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
Expand Down
4 changes: 2 additions & 2 deletions Sources/DictionaryStorage/DictionaryStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
106 changes: 69 additions & 37 deletions Sources/DictionaryStorageMacros/StringRepresentationMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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]
}
}
Expand All @@ -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)
]
}
Expand Down Expand Up @@ -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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ final class StringRepresentationMacroTests: XCTestCase {
case other(String)

var rawValue: String {
stringValue
}

var stringValue: String {
switch self {
case .tourist:
return "tourist"
Expand Down Expand Up @@ -94,6 +98,10 @@ final class StringRepresentationMacroTests: XCTestCase {
case other(String)

var rawValue: String {
stringValue
}

var stringValue: String {
switch self {
case .text:
return "text"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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"
Expand Down
11 changes: 11 additions & 0 deletions Tests/DictionaryStorageTests/StringRepresentationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
}
Loading