ソースを参照

feat: 细化 Codable、Decodable 和 Encodable 的判断

陈文艺 5 ヶ月 前
コミット
544c495f97

+ 42 - 17
Sources/AutoCodableMacro/AutoCodableMacro.swift

@@ -24,6 +24,24 @@ public struct AutoCodableMacro: MemberMacro {
             return []
         }
         
+        // 解析类型遵循的协议列表
+        let inheritedProtocols = typeDecl.inheritanceClause?.inheritedTypes
+            .compactMap { $0.type.as(IdentifierTypeSyntax.self)?.name.text } ?? []
+        
+        // 判断是否遵循 Codable/Decodable/Encodable
+        let conformsToCodable = inheritedProtocols.contains("Codable")
+        let conformsToDecodable = inheritedProtocols.contains("Decodable") || conformsToCodable
+        let conformsToEncodable = inheritedProtocols.contains("Encodable") || conformsToCodable
+        
+        // 校验:至少遵循一个协议,否则报错
+        guard conformsToDecodable || conformsToEncodable else {
+            context.diagnose(Diagnostic(
+                node: node,
+                message: AutoCodableMacroError.protocolError
+            ))
+            return []
+        }
+        
         // 提取所有存储属性
         let properties: [(name: String, type: String)] = typeDecl.memberBlock.members.compactMap { member -> (name: String, type: String)? in
             // 1. 检查是否是变量声明
@@ -61,43 +79,47 @@ public struct AutoCodableMacro: MemberMacro {
             )
         }
         
+        var syntax: [DeclSyntax] = []
         // 生成 CodingKeys 枚举
         let codingKeys = try EnumDeclSyntax("enum CodingKeys: String, CodingKey") {
             for property in properties {
                 DeclSyntax("case \(raw: property.name)")
             }
         }
+        syntax.append(DeclSyntax(codingKeys))
         
-        // 生成 init(from:) 方法
-        let bodyBuilder: SyntaxNodeString = declaration is ClassDeclSyntax ? "required init(from decoder: Decoder) throws" : "init(from decoder: Decoder) throws"
-        let initFrom = try InitializerDeclSyntax(bodyBuilder) {
-            CodeBlockItemListSyntax {
-                "let container = try decoder.container(keyedBy: CodingKeys.self)"
-                for property in properties {
+        if conformsToDecodable {
+            // 生成 init(from:) 方法
+            let bodyBuilder: SyntaxNodeString = declaration is ClassDeclSyntax ? "required init(from decoder: Decoder) throws" : "init(from decoder: Decoder) throws"
+            let initFrom = try InitializerDeclSyntax(bodyBuilder) {
+                CodeBlockItemListSyntax {
+                    "let container = try decoder.container(keyedBy: CodingKeys.self)"
+                    for property in properties {
                     """
                     if let value = try? container.decode(\(raw: property.type).self, forKey: .\(raw: property.name)) {
                         self.\(raw: property.name) = value
                     }
                     """
+                    }
                 }
             }
+            syntax.append(DeclSyntax(initFrom))
         }
         
-        // 生成 encode(to:) 方法
-        let encodeTo = try FunctionDeclSyntax("func encode(to encoder: Encoder) throws") {
-            CodeBlockItemListSyntax {
-                "var container = encoder.container(keyedBy: CodingKeys.self)"
-                for property in properties {
-                    "try container.encode(\(raw: property.name), forKey: .\(raw: property.name))"
+        if conformsToEncodable {
+            // 生成 encode(to:) 方法
+            let encodeTo = try FunctionDeclSyntax("func encode(to encoder: Encoder) throws") {
+                CodeBlockItemListSyntax {
+                    "var container = encoder.container(keyedBy: CodingKeys.self)"
+                    for property in properties {
+                        "try container.encode(\(raw: property.name), forKey: .\(raw: property.name))"
+                    }
                 }
             }
+            syntax.append(DeclSyntax(encodeTo))
         }
         
-        return [
-            DeclSyntax(codingKeys),
-            DeclSyntax(initFrom),
-            DeclSyntax(encodeTo)
-        ]
+        return syntax
     }
 }
 
@@ -105,6 +127,7 @@ public struct AutoCodableMacro: MemberMacro {
 enum AutoCodableMacroError: DiagnosticMessage {
     case onlyClassesAndStructs
     case noStoredProperties
+    case protocolError
     
     var message: String {
         switch self {
@@ -112,6 +135,8 @@ enum AutoCodableMacroError: DiagnosticMessage {
             "AutoCodable can only be applied to classes and structs"
         case .noStoredProperties:
             "Type has no stored properties; Codable implementation will be empty"
+        case .protocolError:
+            "类型必须遵循 Codable、Decodable 或 Encodable"
         }
     }
     

+ 1 - 1
Tests/AutoCodableTests/AutoCodableTests.swift

@@ -2,7 +2,7 @@
 import AutoCodable
 
 @AutoCodable
-class User {
+class User: Codable {
     var id: Int = 0
     var name: String = ""
     var email: String?