陈文艺 5 ヶ月 前
コミット
71f29b03dd

+ 8 - 0
.gitignore

@@ -0,0 +1,8 @@
+.DS_Store
+/.build
+/Packages
+xcuserdata/
+DerivedData/
+.swiftpm/configuration/registries.json
+.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
+.netrc

+ 15 - 0
Package.resolved

@@ -0,0 +1,15 @@
+{
+  "originHash" : "186ddcb0a7881a386ee2b656dbca86112b89e7b5f3d310e3f96a8d91e255d62c",
+  "pins" : [
+    {
+      "identity" : "swift-syntax",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/apple/swift-syntax.git",
+      "state" : {
+        "revision" : "64889f0c732f210a935a0ad7cda38f77f876262d",
+        "version" : "509.1.1"
+      }
+    }
+  ],
+  "version" : 3
+}

+ 46 - 0
Package.swift

@@ -0,0 +1,46 @@
+// swift-tools-version: 6.1
+// The swift-tools-version declares the minimum version of Swift required to build this package.
+
+import PackageDescription
+import CompilerPluginSupport
+import Foundation
+
+let package = Package(
+    name: "AutoCodable",
+    platforms: [
+        .macOS(.v10_15),
+        .iOS(.v13)
+    ],
+    products: [
+        // Products define the executables and libraries a package produces, making them visible to other packages.
+        .library(
+            name: "AutoCodable",
+            targets: ["AutoCodable"]),
+    ],
+    dependencies: [
+        .package(
+            url: "https://github.com/apple/swift-syntax.git",
+            from: "509.0.0"
+        ),
+    ],
+    targets: [
+        // Targets are the basic building blocks of a package, defining a module or a test suite.
+        // Targets can depend on other targets in this package and products from dependencies.
+        .target(
+            name: "AutoCodable",
+            dependencies: ["AutoCodableMacro"]
+        ),
+        .macro(
+            name: "AutoCodableMacro",
+            dependencies: [
+                .product(name: "SwiftSyntax", package: "swift-syntax"),
+                .product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
+                .product(name: "SwiftCompilerPlugin", package: "swift-syntax"),
+            ]
+        ),
+        .testTarget(
+            name: "AutoCodableTests",
+            dependencies: ["AutoCodable"]
+        )
+    ]
+)

+ 6 - 0
Sources/AutoCodable/AutoCodable.swift

@@ -0,0 +1,6 @@
+/// 自动为结构体生成 Codable 实现的宏属性
+@attached(member, names: named(CodingKeys), named(init(from:)), named(encode(to:)))
+public macro AutoCodable() = #externalMacro(
+    module: "AutoCodableMacro",  // 宏所在的模块名(需与工程设置一致)
+    type: "AutoCodableMacro"
+)

+ 120 - 0
Sources/AutoCodableMacro/AutoCodableMacro.swift

@@ -0,0 +1,120 @@
+// The Swift Programming Language
+// https://docs.swift.org/swift-book
+import SwiftSyntax
+import SwiftSyntaxBuilder
+import SwiftSyntaxMacros
+import SwiftDiagnostics
+import Foundation
+
+// 宏实现:自动生成 Codable 协议的实现
+public struct AutoCodableMacro: MemberMacro {
+    public static func expansion(
+        of node: AttributeSyntax,
+        providingMembersOf declaration: some DeclGroupSyntax,
+        in context: some MacroExpansionContext
+    ) throws -> [DeclSyntax] {
+        // 仅支持类和结构体
+        guard let typeDecl: DeclGroupSyntax = declaration.as(ClassDeclSyntax.self) ?? declaration.as(StructDeclSyntax.self) else {
+            context.diagnose(
+                Diagnostic(
+                    node: node,
+                    message: AutoCodableMacroError.onlyClassesAndStructs
+                )
+            )
+            return []
+        }
+        
+        // 提取所有存储属性
+        let properties: [(name: String, type: String)] = typeDecl.memberBlock.members.compactMap { member -> (name: String, type: String)? in
+            // 1. 检查是否是变量声明
+            guard let variableDecl = member.decl.as(VariableDeclSyntax.self) else {
+                return nil
+            }
+            
+            // 2. 确保只处理单个属性的声明
+            guard variableDecl.bindings.count == 1,
+                  let binding = variableDecl.bindings.first else {
+                return nil
+            }
+            
+            // 3. 提取属性名称
+            guard let identifierPattern = binding.pattern.as(IdentifierPatternSyntax.self) else {
+                return nil
+            }
+            let name = identifierPattern.identifier.text
+            
+            // 4. 提取属性类型
+            guard let typeAnnotation = binding.typeAnnotation else {
+                return nil
+            }
+            let type = typeAnnotation.type.description.trimmingCharacters(in: .whitespacesAndNewlines)
+            
+            return (name: name, type: type)
+        }
+        
+        if properties.isEmpty {
+            context.diagnose(
+                Diagnostic(
+                    node: node,
+                    message: AutoCodableMacroError.noStoredProperties
+                )
+            )
+        }
+        
+        // 生成 CodingKeys 枚举
+        let codingKeys = try EnumDeclSyntax("enum CodingKeys: String, CodingKey") {
+            for property in properties {
+                DeclSyntax("case \(raw: property.name)")
+            }
+        }
+        
+        // 生成 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
+                    }
+                    """
+                }
+            }
+        }
+        
+        // 生成 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))"
+                }
+            }
+        }
+        
+        return [
+            DeclSyntax(codingKeys),
+            DeclSyntax(initFrom),
+            DeclSyntax(encodeTo)
+        ]
+    }
+}
+
+// 错误提示定义
+enum AutoCodableMacroError: DiagnosticMessage {
+    case onlyClassesAndStructs
+    case noStoredProperties
+    
+    var message: String {
+        switch self {
+        case .onlyClassesAndStructs:
+            "AutoCodable can only be applied to classes and structs"
+        case .noStoredProperties:
+            "Type has no stored properties; Codable implementation will be empty"
+        }
+    }
+    
+    var diagnosticID: MessageID { .init(domain: "Codable", id: "\(self)") }
+    var severity: DiagnosticSeverity { .error }
+}

+ 16 - 0
Sources/AutoCodableMacro/AutoCodablePlugin.swift

@@ -0,0 +1,16 @@
+//
+//  AutoCodablePlugin.swift
+//  AutoCodable
+//
+//  Created by OneeChan on 2025/10/8.
+//
+
+import SwiftSyntaxMacros
+import SwiftCompilerPlugin
+
+@main
+struct AutoCodablePlugin: CompilerPlugin {
+    let providingMacros: [Macro.Type] = [
+        AutoCodableMacro.self
+    ]
+}

+ 11 - 0
Tests/AutoCodableTests/AutoCodableTests.swift

@@ -0,0 +1,11 @@
+// 导入宏定义的模块
+import AutoCodable
+
+@AutoCodable
+class User {
+    var id: Int = 0
+    var name: String = ""
+    var email: String?
+    var isActive: Bool = false
+    
+}