diff --git a/.github/config/spellcheck-wordlist.txt b/.github/config/spellcheck-wordlist.txt index f2cf7ac567..5f629d62e2 100644 --- a/.github/config/spellcheck-wordlist.txt +++ b/.github/config/spellcheck-wordlist.txt @@ -1,24 +1,52 @@ +ActorVariable +APIs +AssociatedVariable +AttributeExpander +Benchmarking Bool -CocoaPods +ClassVariable +Codable CodedIn CodingKey +CodingKeys +CodingKeysMap +CocoaPods +ConstraintGenerator DateCoder +Decodable +DocumentationExtension +Encodable +EnumCaseVariable +EnumSwitcherVariable +EnumVariable +ExprRegistration Github HelperCoder HelperCoders +IgnoreEncoding JSON LossySequenceCoder -Codable -Encodable -Decodable MetaCodable +MetaCodable's MetaProtocolCodable Midbin +PathRegistration +PluginCore +Podfile +Pre +PropertyVariableTreeNode README Refactorings +Runtime SPM +SequenceCoder +SwiftData +SwiftFormat +SwiftUI SwiftyLab TabNavigator +Trie +VSCode ValueCoder Xcode ae @@ -27,8 +55,15 @@ bc boolean cb cd +cocoapods +conformances +customizable da +datasets eb +enum +enums +expander faq formatters getter @@ -39,30 +74,20 @@ https initializer initializers json +lowercasing macOS mahunt memberwise +mergeBehavior +preprocessing sexualized socio soumya structs swiftpackageindex tvOS +typealias variadic vscode watchOS www -typealias -customizable -enum -enums -conformances -Podfile -cocoapods -DocumentationExtension -mergeBehavior -lowercasing -SwiftData -SwiftUI -IgnoreEncoding -VSCode diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000000..4080859f31 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,4 @@ +We follow swift camel case for naming folders and files. +We use swift-format to for formatting and linting. Custom formatting rules are defined in .swift-format file. +We use swift testing to write tests. +Always remove empty spaces in generated code. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 373c317b30..8ed2b26f53 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,9 +5,24 @@ Please read it before you start participating. _See also: [Contributor Covenant Code of Conduct](CODE_OF_CONDUCT.md)_ +## Developer Documentation + +Detailed documentation for contributors is available in the [Contributing](Contributing/README.md) folder: + +- [Architecture Overview](Contributing/ARCHITECTURE.md) - Core components and system design +- [Macro Processing Pipeline](Contributing/MACRO_PROCESSING.md) - Class hierarchy and code generation +- [Coding Strategies](Contributing/CODING_STRATEGIES.md) - Implementation patterns and helper systems +- [Build Plugin System](Contributing/BUILD_PLUGIN.md) - Plugin architecture and integration +- [Testing and Development](Contributing/TESTING.md) - Testing methodology and best practices +- [Troubleshooting](Contributing/TROUBLESHOOTING.md) - Common issues and solutions + ## Submitting Pull Requests -You can contribute by fixing bugs or adding new features. For larger code changes, we first recommend discussing them in our [Github issues](https://github.com/SwiftyLab/MetaCodable/issues). When submitting a pull request, please add relevant tests and ensure your changes don't break any existing tests (see [Automated Tests](#automated-tests) below). +You can contribute by fixing bugs or adding new features. For larger code changes, we first recommend: +1. Review the [Architecture Overview](Contributing/ARCHITECTURE.md) to understand the system +2. Discuss your proposed changes in our [Github issues](https://github.com/SwiftyLab/MetaCodable/issues) +3. Read the relevant documentation in the [Contributing](Contributing/README.md) folder +4. Submit your pull request with appropriate tests (see [Testing](Contributing/TESTING.md)) ### Things you will need @@ -39,7 +54,9 @@ open $PATH_TO_XCODE_INSTALLATION --env METACODABLE_CI=1 ### Automated Tests -GitHub action is already setup to run tests on pull requests targeting `main` branch. However, to reduce heavy usage of GitHub runners, run the following commands in your terminal to test: +GitHub action is already setup to run tests on pull requests targeting `main` branch. For detailed testing instructions and methodology, see our [Testing Guide](Contributing/TESTING.md). + +To run tests locally and reduce usage of GitHub runners: | Test category | With [Node] | Manually | | --- | --- | --- | diff --git a/Contributing/ARCHITECTURE.md b/Contributing/ARCHITECTURE.md new file mode 100644 index 0000000000..92fea5aa23 --- /dev/null +++ b/Contributing/ARCHITECTURE.md @@ -0,0 +1,89 @@ +# MetaCodable Architecture + +This document provides a high-level overview of MetaCodable's architecture and implementation details. + +## Overview + +MetaCodable is a Swift macro-based framework that supercharges Swift's `Codable` implementations. It uses the power of Swift macros to generate sophisticated coding logic while keeping the API simple and declarative. + +## Core Components + +```mermaid +graph TD + A[MetaCodable Core] --> B[Macro System] + A --> C[Helper Coders] + A --> D[Protocol Generation] + B --> E[Attribute Processing] + B --> F[Code Generation] + C --> G[Built-in Coders] + C --> H[Custom Coders] + D --> I[Build Tool Plugin] + D --> J[Dynamic Codable] + + style A fill:#f9f,stroke:#333,stroke-width:4px +``` + +### 1. Macro System +The core of MetaCodable is built around Swift's macro system, primarily using: +- `@Codable` - The main attribute macro that drives the code generation +- `@CodedAt`, `@CodedIn`, `@CodedBy` etc. - Attribute macros for customizing coding behavior +- `PluginCore` - Core functionality for macro expansions and code generation + +### 2. Helper Coders System +Implements extensible coding strategies through: +- `HelperCoder` protocol for custom coding implementations +- Built-in coders like `ValueCoder`, `SequenceCoder`, etc. +- Support for custom coders through `@CodedBy` attribute + +### 3. Protocol Generation System +Provides dynamic protocol conformance through: +- `MetaProtocolCodable` build tool plugin +- `DynamicCodable` type for generating protocol implementations +- Source code parsing and synthesis capabilities + +## Data Flow + +```mermaid +sequenceDiagram + participant User + participant Macros + participant CodeGen + participant Runtime + + User->>Macros: Adds @Codable attribute + Macros->>CodeGen: Process attributes + CodeGen->>CodeGen: Generate implementation + CodeGen->>Runtime: Emit Codable conformance + Runtime->>User: Use generated code +``` + +## Key Features + +1. **Flexible Key Mapping** + - Custom CodingKey values per property + - Nested coding key support + - Multiple coding key paths + +2. **Smart Defaults and Error Handling** + - Default value specification + - Custom error handling + - Missing value handling + +3. **Protocol and Type Support** + - Enum support with various tagging styles + - Protocol type generation + - Generic type support + +## Implementation Details + +### Attribute Processing +The framework processes attributes in multiple phases: +1. Attribute validation and preprocessing +2. Context gathering and analysis +3. Code generation and emission + +### Code Generation +The code generation system follows these principles: +1. Maintains source compatibility +2. Generates optimal, efficient code +3. Supports full customization through attributes diff --git a/Contributing/BUILD_PLUGIN.md b/Contributing/BUILD_PLUGIN.md new file mode 100644 index 0000000000..d9b626b77d --- /dev/null +++ b/Contributing/BUILD_PLUGIN.md @@ -0,0 +1,188 @@ +# Build Tool Plugin System + +This document explains the implementation details of MetaCodable's build tool plugin system. + +## Overview + +The build tool plugin system is primarily implemented through the `MetaProtocolCodable` plugin which generates protocol implementations at build time. + +## Architecture + +```mermaid +graph TD + A[MetaProtocolCodable Plugin] --> B[Source Scanner] + A --> C[Parser] + A --> D[Generator] + B --> E[Swift Files] + C --> F[Intermediate Representation] + D --> G[Generated Code] + E --> C + F --> D +``` + +## Components + +### 1. Plugin Core + +The plugin system is built on Swift Package Manager's plugin architecture and consists of: + +```swift +@main +struct MetaProtocolCodable: BuildToolPlugin { + func createBuildCommands( + context: PluginContext, + target: Target + ) throws -> [Command] +} +``` + +Key responsibilities: +- Configuration management +- Build command generation +- File management + +### 2. Source Scanner + +The source scanner: +- Identifies relevant Swift files +- Extracts protocol information +- Handles dependencies + +### 3. Parser + +The parser component: +- Processes Swift syntax +- Extracts type information +- Generates intermediate representations + +### 4. Generator + +The code generator: +- Synthesizes implementations +- Handles type safety +- Manages symbol resolution + +## Implementation Details + +### Configuration + +The plugin uses a configuration system that can be customized through: +- JSON files +- Property lists +- Command-line arguments + +Example configuration: +```json +{ + "scanDependencies": true, + "outputFormat": "swift", + "generatedFilePrefix": "Generated", + "modules": ["CoreModule", "UtilityModule"] +} +``` + +### Build Process + +1. **Initialization** + - Load configuration + - Set up build environment + - Prepare output directories + +2. **Scanning** + - Identify source files + - Extract protocol definitions + - Build dependency graph + +3. **Generation** + - Create intermediate files + - Generate implementations + - Output final code + +### File Structure + +The plugin generates files in a structured manner: + +``` +PluginOutput/ +├── Intermediate/ +│ ├── Module1.json +│ └── Module2.json +├── Generated/ +│ ├── Protocol1.swift +│ └── Protocol2.swift +└── BuildArtifacts/ + └── metadata.json +``` + +## Usage in Development + +### 1. Plugin Integration + +Add to your `Package.swift`: +```swift +.plugin( + name: "MetaProtocolCodable", + capability: .buildTool(), + dependencies: ["ProtocolGen"] +) +``` + +### 2. Protocol Definition + +Mark protocols for generation: +```swift +@DynamicCodable +protocol CustomProtocol { + var id: String { get } + var data: Data { get } +} +``` + +### 3. Build Process + +The plugin automatically: +1. Scans for marked protocols +2. Generates implementations +3. Includes generated code in build + +## Extending the Plugin + +### Custom Generators + +Create custom generators by: +1. Implementing the generator protocol +2. Registering with the plugin +3. Handling custom types + +Example: +```swift +struct CustomGenerator: CodeGenerator { + func generate(for type: TypeDefinition) throws -> String { + // Custom generation logic + } +} +``` + +### Build Process Hooks + +The plugin provides hooks for: +- Pre-build processing +- Post-generation validation +- Error handling + +## Best Practices + +1. **Performance** + - Use incremental generation + - Cache intermediate results + - Optimize file I/O + +2. **Maintainability** + - Document generated code + - Version control integration + - Clear error messages + +3. **Integration** + - Clean build support + - IDE integration + - Debug support diff --git a/Contributing/CODING_STRATEGIES.md b/Contributing/CODING_STRATEGIES.md new file mode 100644 index 0000000000..7815c556a7 --- /dev/null +++ b/Contributing/CODING_STRATEGIES.md @@ -0,0 +1,132 @@ +# Coding Strategies + +This document details the various coding strategies implemented in MetaCodable and how to extend them. + +## Core Strategies + +### Key Transformation + +```mermaid +graph LR + A[Source Key] --> B[Transformation] + B --> C[Target Key] + B --> D[snake_case] + B --> E[camelCase] + B --> F[PascalCase] + B --> G[SCREAMING_SNAKE_CASE] +``` + +Key transformations are handled through: +- `CodingKeys(_:)` attribute for global key style +- `CodedAt(_:)` for individual property key mapping +- Built-in support for various case styles + +### Nested Coding + +MetaCodable provides several strategies for handling nested data structures: + +1. **Flattened Models** +```swift +@Codable +struct Coordinate { + var latitude: Double + var longitude: Double + @CodedIn("additionalInfo") + var elevation: Double +} +``` + +This automatically handles JSON like: +```json +{ + "latitude": 0, + "longitude": 0, + "additionalInfo": { + "elevation": 0 + } +} +``` + +2. **Path-Based Access** +- Use `CodedAt(_:)` for direct path access +- Support for multiple path components +- Automatic container management + +### Helper Coders + +The `HelperCoder` protocol is the foundation for custom coding strategies: + +```swift +protocol HelperCoder { + associatedtype Coded + func decode(from decoder: Decoder) throws -> Coded + func encode(_ value: Coded, to encoder: Encoder) throws +} +``` + +Built-in helper coders include: +1. **ValueCoder**: Direct value coding +2. **SequenceCoder**: Collection handling +3. **DateCoder**: Date format handling +4. **Base64Coder**: Binary data encoding + +## Extending the System + +### Creating Custom Coders + +1. Implement the `HelperCoder` protocol +2. Handle encoding and decoding logic +3. Use with `@CodedBy` attribute + +Example: +```swift +struct CustomDateCoder: HelperCoder { + let formatter: DateFormatter + + func decode(from decoder: Decoder) throws -> Date { + let string = try String(from: decoder) + guard let date = formatter.date(from: string) else { + throw DecodingError.dataCorruptedError( + in: decoder as! any SingleValueDecodingContainer, + debugDescription: "Invalid date format" + ) + } + return date + } + + func encode(_ value: Date, to encoder: Encoder) throws { + try formatter.string(from: value).encode(to: encoder) + } +} +``` + +### Protocol Generation + +The MetaProtocolCodable build tool plugin supports: + +1. **Dynamic Protocol Implementation** + - Automatic conformance generation + - Type-safe protocol handling + - Cross-module support + +2. **Build Process Integration** + - Source code scanning + - Implementation generation + - Build-time validation + +## Best Practices + +1. **Key Naming** + - Use consistent naming conventions + - Leverage built-in transformations + - Document custom key mappings + +2. **Error Handling** + - Provide meaningful default values + - Use appropriate error strategies + - Handle missing values gracefully + +3. **Performance** + - Use appropriate container types + - Minimize nesting depth + - Leverage built-in optimizations diff --git a/Contributing/MACRO_PROCESSING.md b/Contributing/MACRO_PROCESSING.md new file mode 100644 index 0000000000..736a7d4203 --- /dev/null +++ b/Contributing/MACRO_PROCESSING.md @@ -0,0 +1,564 @@ +# Macro Processing Pipeline + +This document details how the PluginCore module processes code through its macro system, from top-level types to their properties and enum cases. + +## Class Hierarchy + +```mermaid +classDiagram + class Variable { + <> + +decoding(context, location) + +encoding(context, location) + } + + class TypeVariable { + <> + +codingKeys(protocols, context) + } + + class PropertyVariable { + <> + +type: TypeSyntax + +name: TokenSyntax + +value: ExprSyntax + +requireDecodable: Bool + +requireEncodable: Bool + } + + class EnumCaseVariable { + <> + +variables: [AssociatedVariable] + } + + class DeclaredVariable { + <> + +init(from:in:) + } + + Variable <|-- TypeVariable + Variable <|-- PropertyVariable + Variable <|-- EnumCaseVariable + + TypeVariable <|-- MemberGroup + TypeVariable <|-- ClassVariable + TypeVariable <|-- ActorVariable + TypeVariable <|-- EnumVariable + + class MemberGroup { + -constraintGenerator: ConstraintGenerator + -node: PropertyVariableTreeNode + -codingKeys: CodingKeysMap + } + + class EnumVariable { + -name: TokenSyntax + -encodeSwitchExpr: ExprSyntax + -cases: [(EnumCaseVariable, CaseValue)] + -switcher: EnumSwitcherVariable + } + + class ClassVariable { + -group: MemberGroup + -inherits: Inherits? + } + + class AttributeExpander { + -variable: TypeVariable + +decoding(context, location) + +encoding(context, location) + +codingKeys(type, protocols, context) + } +``` + +## Processing Pipeline + +### 1. Entry Point - Plugin and AttributeExpander + +The macro processing starts with the `MetaCodablePlugin` ([`MacroPlugin/Plugin.swift`](../Sources/MacroPlugin/Plugin.swift)): + +```swift +@main +struct MetaCodablePlugin: CompilerPlugin { + let providingMacros: [Macro.Type] = [ + CodedAt.self, + CodedIn.self, + CodedBy.self, + Default.self, + // ... other macros + ] +} +``` + +The `AttributeExpander` ([`PluginCore/Expansion/AttributeExpander.swift`](../Sources/PluginCore/Expansion/AttributeExpander.swift)) then processes declarations: + +```mermaid +graph TD + A[AttributeExpander] --> B[Parse Declaration] + B --> C[Create TypeVariable] + C --> D[Generate Coding Implementation] + D --> E[Generate CodingKeys] + D --> F[Generate Memberwise Init] +``` + +Key responsibilities: +- Creates appropriate `TypeVariable` instances based on declaration type +- Manages variable and `CodingKey` data registration +- Coordinates code generation for Codable conformance + +The expander implementation starts in the constructor: + +```swift +init?( + for declaration: some DeclGroupSyntax, + in context: some MacroExpansionContext +) { + guard + let decl = declaration as? any VariableSyntax, + case let variable = decl.codableVariable( + in: context + ) as any DeclaredVariable, + let typeVar = variable as? any TypeVariable + else { return nil } + self.variable = typeVar + self.options = .init(for: declaration) +} + +### 2. Type Variable Creation + +Different types are handled by specialized implementations: + +1. **MemberGroup** (for structs, as base implementation for classes and actors) +```swift +struct MemberGroup: TypeVariable { + let constraintGenerator: ConstraintGenerator + let node: PropertyVariableTreeNode + let codingKeys: CodingKeysMap +} +``` + +2. **ClassVariable** (for classes) +```swift +struct ClassVariable: TypeVariable { + let group: MemberGroup + let decl: ClassDeclSyntax + var inherits: Inherits? +} +``` + +3. **EnumVariable** (for enums) +```swift +struct EnumVariable: TypeVariable { + let name: TokenSyntax + let encodeSwitchExpr: ExprSyntax + let cases: [(variable: EnumCaseVariable, value: CaseValue)] + let switcher: EnumSwitcherVariable +} +``` + +2. **ActorVariable** (for actors) +```swift +struct ActorVariable: TypeVariable { + let base: MemberGroup +} +``` + +### 3. Property Processing + +Properties are processed through specialized variable types that handle different aspects of coding. The main components are defined in [`PluginCore/Variables/Property`](../Sources/PluginCore/Variables/Property/): + +#### Basic Property Variable +[`BasicPropertyVariable.swift`](../Sources/PluginCore/Variables/Property/BasicPropertyVariable.swift) handles standard property decoding/encoding: + +```swift +struct BasicPropertyVariable: PropertyVariable { + let name: TokenSyntax + let type: TypeSyntax + let value: ExprSyntax? + let decodePrefix: TokenSyntax + let encodePrefix: TokenSyntax + + func decoding( + in context: some MacroExpansionContext, + from location: PropertyCodingLocation + ) -> CodeBlockItemListSyntax { + switch location { + case .coder(let decoder, let passedMethod): + // Direct decoder implementation + case .container(let container, let key, let passedMethod): + // Container-based decoding + } + } + + func encoding( + in context: some MacroExpansionContext, + to location: PropertyCodingLocation + ) -> CodeBlockItemListSyntax { + // Similar pattern for encoding + } +} +``` + +#### Helper Coded Variable +[`HelperCodedVariable.swift`](../Sources/PluginCore/Variables/Property/HelperCodedVariable.swift) manages custom coding helpers: + +```swift +struct HelperCodedVariable: ComposedVariable, PropertyVariable +where Wrapped: DefaultPropertyVariable { + enum Options { + case helper(_ expr: ExprSyntax) + case helperAction(_ action: ExprSyntax, _ params: [Parameter]) + } + + func decoding( + in context: some MacroExpansionContext, + from location: PropertyCodingLocation + ) -> CodeBlockItemListSyntax { + // Generate helper-based decoding implementation + } +} +``` + +#### Aliased Property Variable +[`AliasedPropertyVariable.swift`](../Sources/PluginCore/Variables/Property/AliasedPropertyVariable.swift) handles properties with multiple coding keys: + +```swift +struct AliasedPropertyVariable: PropertyVariable, ComposedVariable { + let additionalKeys: OrderedSet + + func decoding( + in context: some MacroExpansionContext, + from location: PropertyCodingLocation + ) -> CodeBlockItemListSyntax { + // Check multiple keys and decode from available one + } +} +``` + +[The processing pipeline follows](../Sources/PluginCore/Variables/Type/MemberGroup.swift#L191) this flow: + +```mermaid +graph TD + A[Property Declaration] --> B[Basic Property Variable] + B --> C[Transform Keys] + C --> D[Check If Ignored Due To Initialized] + D --> E[Register Key Path] + E --> F[Add Coding Strategies] + F --> G[Add Helper Coders] + G --> H[Add Default Values] + H --> I[Check Initialization] + I --> J[Check Whether Decoding/Encoding Ignored] +``` + + + +Core Protocol: +```swift +protocol PropertyVariable: NamedVariable { + var type: TypeSyntax { get } + var name: TokenSyntax { get } + var value: ExprSyntax? { get } + var requireDecodable: Bool? { get } + var requireEncodable: Bool? { get } + + func decoding(in: MacroExpansionContext, from: PropertyCodingLocation) -> CodeBlockItemListSyntax + func encoding(in: MacroExpansionContext, to: PropertyCodingLocation) -> CodeBlockItemListSyntax +} +``` + +### 4. Enum Case Processing + +The [`PluginCore/Variables/Type/EnumVariable.swift`](../Sources/PluginCore/Variables/Type/EnumVariable.swift) module handles enum processing: + +```swift +struct EnumVariable: TypeVariable { + let name: TokenSyntax + let encodeSwitchExpr: ExprSyntax + let cases: [(variable: EnumCaseVariable, value: CaseValue)] + let switcher: EnumSwitcherVariable + + func decoding( + in context: some MacroExpansionContext, + from location: TypeCodingLocation + ) -> TypeGenerated? { + // Generate enum decoding implementation + } + + func encoding( + in context: some MacroExpansionContext, + to location: TypeCodingLocation + ) -> TypeGenerated? { + // Generate enum encoding implementation + } +} +``` + +[The processing follows](../Sources/PluginCore/Variables/Type/EnumVariable.swift#L133) this flow: + +```mermaid +graph TD + A[Enum Declaration] --> B[Create EnumVariable] + B --> C[Process Cases] + C --> D[Create Switcher Based On Tagging] + D --> E[Process Associated Values] + + subgraph "Case Processing" + C --> F[Basic Enum Case] + F --> G[Transform Keys] + G --> H[Add Coding Strategies] + end + + subgraph "Switcher Processing" + D --> I[Externally Tagged Enum] + I --> J[Add Internal Tag Key] + J --> K[Add Internal Tag Helper Coders] + K --> L[Add Adjacent Tag Key] + L --> M[Check No Tags] + end + + subgraph "Associated Value Processing" + E --> N[Basic Associated Variable] + N --> O[Transform Keys] + O --> P[Check If Ignored Due To Initialized] + P --> Q[Register Key Path] + Q --> R[Add Coding Strategies] + R --> S[Add Helper Coders] + S --> T[Add Default Values] + T --> U[Check Whether Decoding/Encoding Ignored] + end +``` + +Key Components: + +1. **EnumSwitcherVariable**: Manages case detection and switching logic +```swift +protocol EnumSwitcherVariable: Variable { + func node(for: EnumCaseVariableDeclSyntax, in: MacroExpansionContext) + func codingKeys(in: MacroExpansionContext) +} +``` + +2. **EnumCaseVariable**: Handles individual case processing +```swift +protocol EnumCaseVariable: Variable { + var variables: [AssociatedVariable] { get } + var name: TokenSyntax { get } + + func decoding(in: MacroExpansionContext, from: EnumCaseCodingLocation) -> CodeBlockItemListSyntax + func encoding(in: MacroExpansionContext, to: EnumCaseCodingLocation) -> CodeBlockItemListSyntax +} +``` + +3. **AssociatedVariable**: A [`PropertyVariable`](#property_variable) that handles associated value processing +```swift +protocol AssociatedVariable: PropertyVariable { + var label: TokenSyntax? { get } +} +``` + +### 5. Code Generation + +The code generation process is coordinated by the `AttributeExpander` ([`PluginCore/Expansion/AttributeExpander.swift`](../Sources/PluginCore/Expansion/AttributeExpander.swift)): + +```swift +struct AttributeExpander { + let variable: any TypeVariable + private let options: Options + + func codableExpansion( + for type: some TypeSyntaxProtocol, + to protocols: [TypeSyntax], + in context: some MacroExpansionContext + ) -> [ExtensionDeclSyntax] { + // Generate extensions for Codable conformance + } +} +``` + +The generation process follows these steps: + +1. **Decoding Generation** - Creates `init(from:)` implementation: +```swift +func decoding(in context: MacroExpansionContext, from location: TypeCodingLocation) -> TypeGenerated? { + let container = location.context.forVariable + + // 1. Generate container access + let containerDecl = generateContainerDeclaration(container) + + // 2. Generate property decoding + for variable in variables where variable.decode ?? true { + let decodingSyntax = variable.decoding(in: context, from: container) + } + + // 3. Add initialization code + let initCode = generateInitialization() + + // 4. Add error handling + return TypeGenerated( + code: containerDecl + decodingSyntax + initCode, + modifiers: modifiers, + whereClause: whereClause + ) +} +``` + +2. **Encoding Generation** - Creates `encode(to:)` implementation: +```swift +func encoding(in context: MacroExpansionContext, to location: TypeCodingLocation) -> TypeGenerated? { + // 1. Create container + let container = createEncodingContainer(location) + + // 2. Generate property encoding + for variable in variables where variable.encode ?? true { + let encodingSyntax = variable.encoding(in: context, to: container) + } + + // 3. Add inheritance handling for classes + if let superClass = inheritedType { + let superEncoding = generateSuperEncoding(superClass) + } + + // 4. Add error handling + return TypeGenerated( + code: containerCode + encodingSyntax + superEncoding, + modifiers: modifiers, + whereClause: whereClause + ) +} +``` + +3. **CodingKeys Generation** - Creates custom key enum: +```swift +func codingKeys(confirmingTo protocols: [TypeSyntax], in context: MacroExpansionContext) -> MemberBlockItemListSyntax { + // 1. Check protocol conformance + let decodable = variable.protocol(named: "Decodable", in: protocols) + let encodable = variable.protocol(named: "Encodable", in: protocols) + + // 2. Generate coding keys enum + let enumDecl = generateCodingKeysEnum() + + // 3. Add custom key mappings + for (property, customKey) in keyMappings { + let caseDecl = generateKeyCase(property, customKey) + } + + return MemberBlockItemListSyntax([enumDecl, casesDecl]) +} +``` + +## Variable Registration System + +The registration system manages the relationship between variables and their coding keys: + +```mermaid +graph TD + A[Registration] --> B[Path Registration] + A --> C[Expr Registration] + + B --> D[Property Variable] + C --> E[Enum Case Variable] + + D --> F[Coding Keys Map] + E --> F +``` + +### Registration Types + +The registration system is implemented in [`PluginCore/Variables/Type/Data/Registration.swift`](../Sources/PluginCore/Variables/Type/Data/Registration.swift) and defines two main registration types: + +1. [**PathRegistration**](../Sources/PluginCore/Variables/Type/Data/Registration.swift#L83): For properties with coding key paths +```swift +/// A type representing property variable registration for code generation. +typealias PathRegistration = Registration +where Var: Variable +``` + +2. [**ExprRegistration**](../Sources/PluginCore/Variables/Type/Data/Registration.swift#L88): For enum cases with expressions +```swift +typealias ExprRegistration = Registration +``` + +## Helper Components + +1. **PropertyVariableTreeNode** ([`PluginCore/Variables/Property/Tree/PropertyVariableTreeNode.swift`](../Sources/PluginCore/Variables/Property/Tree/PropertyVariableTreeNode.swift#L1-L29)) + - Implements a Trie-based tree structure for managing nested coding paths + - Core implementation: + ```swift + final class PropertyVariableTreeNode: Variable, VariableTreeNode { + var variables: [any PropertyVariable] = [] + var children: OrderedDictionary = [:] + private var decodingContainer: TokenSyntax? + var immutableEncodeContainer: Bool = false + } + ``` + + - **Key Features**: + - **Tree-based Registration** ([L60-88](../Sources/PluginCore/Variables/Property/Tree/PropertyVariableTreeNode+CodingData.swift#L60-L88)) + ```swift + mutating func register( + variable: Variable, + keyPath: [CodingKeysMap.Key], + immutableEncodeContainer: Bool = false + ) + ``` + - **Decoding Generation** ([L29-56](../Sources/PluginCore/Variables/Property/Tree/PropertyVariableTreeNode.swift#L29-L56)) + - **Encoding Generation** ([L153-180](../Sources/PluginCore/Variables/Property/Tree/PropertyVariableTreeNode.swift#L153-L180)) + +2. **CodingKeysMap** ([`PluginCore/Variables/Type/Data/CodingKeysMap/CodingKeysMap.swift`](../Sources/PluginCore/Variables/Type/Data/CodingKeysMap/CodingKeysMap.swift#L1-L32)) + - Manages key name generation and uniqueness + - Main implementation: + ```swift + final class CodingKeysMap { + let typeName: TokenSyntax + let fallbackTypeName: TokenSyntax? + private var data: OrderedDictionary = [:] + private var usedKeys: Set = [] + + var type: ExprSyntax { + guard data.isEmpty, usedKeys.isEmpty, let fallbackTypeName + else { return "\(typeName).self" } + return "\(fallbackTypeName).self" + } + } + ``` + + - **Key Features**: + - **Key Registration** ([L96-122](../Sources/PluginCore/Variables/Type/Data/CodingKeysMap/CodingKeysMap.swift#L96-L122)) + - **Case Generation** ([L121-147](../Sources/PluginCore/Variables/Type/Data/CodingKeysMap/CodingKeysMap.swift#L121-L147)) + - **Key Transformation** ([L77-96](../Sources/PluginCore/Variables/Type/Data/CodingKeysMap/CodingKeysMap.swift#L77-L96)) + +3. **ConstraintGenerator** ([`PluginCore/Variables/Type/Data/ConstraintGenerator.swift`](../Sources/PluginCore/Variables/Type/Data/ConstraintGenerator.swift#L1-L35)) + - Manages generic constraints for Codable conformance + - Core implementation: + ```swift + struct ConstraintGenerator { + var typeArguments: [TokenSyntax] = [] + + func codingClause( + forRequirementPath path: KeyPath, + withVariables variables: [any PropertyVariable], + conformingTo protocol: TypeSyntax + ) -> GenericWhereClauseSyntax? + } + ``` + + - **Key Features**: + - **Generic Parameter Handling** ([L35-59](../Sources/PluginCore/Variables/Type/Data/ConstraintGenerator.swift#L35-L59)) + - **Protocol Requirements** ([L59-89](../Sources/PluginCore/Variables/Type/Data/ConstraintGenerator.swift#L59-L89)) + +## Best Practices + +1. **Variable Processing** + - Always validate variable declarations + - Handle optional and required cases + - Process nested containers properly + +2. **Code Generation** + - Generate optimal, non-redundant code + - Handle inheritance correctly + - Maintain proper error handling + +3. **Type Safety** + - Validate type constraints + - Check protocol conformance + - Handle generic parameters diff --git a/Contributing/README.md b/Contributing/README.md new file mode 100644 index 0000000000..c18f0d91c9 --- /dev/null +++ b/Contributing/README.md @@ -0,0 +1,145 @@ +# Contributing Documentation + +This directory contains detailed documentation about MetaCodable's implementation and development practices. + +## Table of Contents + +1. [Architecture Overview](ARCHITECTURE.md) + - Core components and system design + - Data flow and processing + - Key architectural decisions + +2. [Macro Processing Pipeline](MACRO_PROCESSING.md) + - Detailed class hierarchy + - Processing steps + - Code generation system + +3. [Coding Strategies](CODING_STRATEGIES.md) + - Implementation of coding strategies + - Helper coders system + - Custom coding patterns + +4. [Build Plugin System](BUILD_PLUGIN.md) + - Plugin architecture + - Build process integration + - Code generation system + +5. [Testing and Development](TESTING.md) + - Testing methodology + - Development workflow + - Best practices + +## Project Structure + +```mermaid +graph TD + A[MetaCodable] --> B[Core Library] + A --> C[Build Plugin] + A --> D[Helper Coders] + + B --> E[Macro System] + B --> F[Core APIs] + + C --> G[Protocol Generation] + C --> H[Build Integration] + + D --> I[Built-in Coders] + D --> J[Custom Coders] +``` + +## Quick Start for Contributors + +1. **Setup Development Environment** + ```bash + git clone https://github.com/SwiftyLab/MetaCodable.git + cd MetaCodable + swift build + ``` + +2. **Run Tests** + ```bash + swift test + ``` + +3. **Documentation Generation** + ```bash + swift package generate-documentation + ``` + +## Development Guidelines + +### Code Style + +- Follow [Swift API Design Guidelines](https://www.swift.org/documentation/api-design-guidelines/) +- Use swift-format for code formatting +- Write comprehensive documentation + +### Pull Request Process + +1. Fork the repository +2. Create a feature branch +3. Add tests and documentation +4. Submit a pull request + +### Version Control Practices + +- Use semantic versioning +- Keep commits focused and atomic +- Write clear commit messages + +## Module Structure + +The project is organized into several key modules: + +1. **MetaCodable Core** + - Main macro implementations + - Core coding functionality + - API definitions + +2. **Helper Coders** + - Built-in coding helpers + - Extension points + - Utility functions + +3. **Build Plugin** + - Protocol generation + - Build system integration + - Code synthesis + +4. **Testing Infrastructure** + - Test helpers + - Macro testing utilities + - Integration tests + +## Documentation Standards + +1. **API Documentation** + - Document all public APIs + - Include usage examples + - Explain edge cases + +2. **Implementation Notes** + - Document complex algorithms + - Explain design decisions + - Note performance considerations + +3. **Test Documentation** + - Describe test scenarios + - Document test data + - Explain test coverage + +## Getting Help + +- Join our community discussions +- Review existing issues +- Check documentation first + +## License and Attribution + +MetaCodable is licensed under the MIT License. See the [LICENSE](../LICENSE) file for details. + +## Additional Resources + +- [Swift Macros Documentation](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/macros) +- [Swift Package Manager Documentation](https://www.swift.org/package-manager/) +- [Codable Documentation](https://developer.apple.com/documentation/swift/codable) diff --git a/Contributing/TESTING.md b/Contributing/TESTING.md new file mode 100644 index 0000000000..3167073c91 --- /dev/null +++ b/Contributing/TESTING.md @@ -0,0 +1,191 @@ +# Testing and Development Guide + +This document outlines the testing methodology and development practices for MetaCodable. + +## Testing Architecture + +```mermaid +graph TD + A[Test Suite] --> B[Unit Tests] + A --> C[Integration Tests] + A --> D[Performance Tests] + B --> E[Macro Tests] + B --> F[Helper Tests] + B --> G[Plugin Tests] + C --> H[End-to-End Tests] + C --> I[Cross-Platform Tests] +``` + +## Test Categories + +### 1. Macro Tests + +Located in `Tests/MetaCodableTests`, these tests verify: +- Macro expansion correctness +- Error diagnostics +- Edge cases handling + +Example test structure: +```swift +struct CodingKeysTests { + @Test + func testBasicExpansion() { + assertMacroExpansion( + """ + @Codable + struct Model { + let property: String + } + """, + expandedSource: // Expected expansion + ) + } +} +``` + +### 2. Helper Coder Tests + +Tests for built-in and custom helper coders: +- Encoding/decoding accuracy +- Error handling +- Performance characteristics + +### 3. Plugin Tests + +Tests for the build tool plugin: +- Configuration handling +- Code generation +- Build process integration + +## Development Workflow + +### 1. Local Development + +Setup: +1. Clone repository +2. Install dependencies +3. Build project: + ```bash + swift build + ``` + +### 2. Testing Process + +Run tests: +```bash +swift test +``` + +Coverage: +```bash +swift test --enable-code-coverage +``` + +### 3. CI/CD Pipeline + +The project uses GitHub Actions for: +- Automated testing +- Cross-platform validation +- Documentation generation + +## Best Practices + +### 1. Code Style + +- Follow Swift API Design Guidelines +- Use SwiftFormat for formatting +- Maintain consistent documentation + +### 2. Testing Guidelines + +1. **Test Organization** + - Group related tests + - Clear test names + - Comprehensive coverage + +2. **Test Data** + - Use realistic examples + - Cover edge cases + - Document test cases + +3. **Performance Testing** + - Benchmark critical paths + - Profile memory usage + - Test with large datasets + +### 3. Documentation + +- Document public APIs +- Provide usage examples +- Update docs with changes + +## Debugging + +### 1. Macro Debugging + +Tools and techniques: +- Print expanded source +- Analyze diagnostics +- Use breakpoints + +Example debugging session: +```swift +#if DEBUG +print(expandedSource) +#endif +``` + +### 2. Plugin Debugging + +Debug build plugin: +1. Enable verbose output +2. Check intermediate files +3. Analyze build logs + +### 3. Runtime Debugging + +Debug coding issues: +1. Enable detailed logging +2. Use debugging proxies +3. Validate data structures + +## Performance Considerations + +### 1. Compile Time + +Optimize for: +- Macro expansion speed +- Plugin execution time +- Build system integration + +### 2. Runtime + +Monitor: +- Encoding/decoding performance +- Memory usage +- Error handling overhead + +### 3. Benchmarking + +Use benchmarks to: +- Track performance +- Identify bottlenecks +- Validate optimizations + +## Contributing Guidelines + +### 1. Pull Requests + +Process: +1. Create feature branch +2. Add tests +3. Update documentation +4. Submit PR + +### 2. Code Review + +Checklist: +- Test coverage +- Performance impact +- API compatibility +- Documentation updates diff --git a/Contributing/TROUBLESHOOTING.md b/Contributing/TROUBLESHOOTING.md new file mode 100644 index 0000000000..2b33d6cdb9 --- /dev/null +++ b/Contributing/TROUBLESHOOTING.md @@ -0,0 +1,213 @@ +# Troubleshooting Guide + +This document provides solutions for common issues encountered while working with MetaCodable. + +## Diagnostic System + +```mermaid +graph TD + A[Issue Detection] --> B[Diagnostic Generation] + B --> C[Error Reporting] + C --> D[Fix-it Suggestions] + + B --> E[Warning Messages] + B --> F[Error Messages] + B --> G[Notes] + + D --> H[Quick Fix] + D --> I[Manual Resolution] +``` + +## Common Issues + +### 1. Macro Expansion Issues + +#### Symptoms +- Macro expansion fails +- Unexpected generated code +- Compilation errors in generated code + +#### Troubleshooting Steps +1. Check macro syntax +2. Verify attribute parameters +3. Review error messages +4. Check for conflicting attributes + +#### Example Resolution +```swift +// Instead of: +@Codable +@CodingKeys(.snake_case) +@CodingKeys(.PascalCase) // Error: Multiple CodingKeys attributes +struct Model { } + +// Use: +@Codable +@CodingKeys(.snake_case) +struct Model { } +``` + +### 2. Build Plugin Problems + +#### Symptoms +- Plugin fails to run +- Missing generated code +- Build errors + +#### Troubleshooting Steps +1. Check plugin configuration +2. Verify source file paths +3. Review build logs +4. Check intermediate files + +### 3. Helper Coder Issues + +#### Symptoms +- Encoding/decoding failures +- Unexpected data transformations +- Runtime errors + +#### Troubleshooting Steps +1. Verify coder implementation +2. Check input/output data +3. Review error handling +4. Test edge cases + +## Error Messages + +### 1. Macro Diagnostics + +Common error messages and their solutions: + +| Error Message | Cause | Solution | +|--------------|-------|----------| +| "Cannot find type..." | Missing type in scope | Import required module | +| "Invalid attribute arguments" | Wrong attribute parameters | Check parameter types | +| "Conflicting attributes" | Multiple incompatible attributes | Remove conflicting attribute | + +### 2. Build Errors + +Common build errors and their solutions: + +| Error | Cause | Solution | +|-------|-------|----------| +| Plugin execution failed | Build tool issue | Check plugin configuration | +| Missing generated files | Source scanning issue | Verify source paths | +| Symbol not found | Link error | Check module imports | + +## Debugging Techniques + +### 1. Macro Debugging + +```swift +// Add debug prints in development: +#if DEBUG +print("Generated code: \(expandedSource)") +#endif +``` + +### 2. Build Plugin Debugging + +Enable verbose output: +```bash +swift build -v +``` + +### 3. Runtime Debugging + +Add logging: +```swift +struct DebugCoder: HelperCoder { + func decode(from decoder: Decoder) throws -> T { + print("Decoding type: \(T.self)") + return try T(from: decoder) + } +} +``` + +## Performance Issues + +### 1. Compile Time + +#### Symptoms +- Slow compilation +- High memory usage +- Build timeouts + +#### Solutions +1. Optimize macro implementations +2. Reduce generated code size +3. Use incremental builds + +### 2. Runtime + +#### Symptoms +- Slow encoding/decoding +- High memory usage +- Poor app performance + +#### Solutions +1. Profile code +2. Optimize algorithms +3. Cache results + +## Best Practices + +### 1. Error Prevention + +- Use type-safe APIs +- Add comprehensive tests +- Document edge cases +- Follow coding guidelines + +### 2. Debugging + +- Enable verbose logging +- Use breakpoints +- Check intermediate states +- Review generated code + +### 3. Performance + +- Profile critical paths +- Benchmark operations +- Monitor resource usage +- Optimize hot spots + +## Getting Help + +### 1. Resources + +- Project documentation +- API reference +- Sample code +- Test cases + +### 2. Community + +- GitHub issues +- Discussion forums +- Stack Overflow +- Community channels + +## Reporting Issues + +When reporting issues: + +1. **Provide Context** + - Swift version + - MetaCodable version + - Operating system + - Build environment + +2. **Include Details** + - Error messages + - Stack traces + - Sample code + - Expected vs actual behavior + +3. **Steps to Reproduce** + - Clear steps + - Minimal example + - Required setup + - Environment details diff --git a/Sources/MetaCodable/MetaCodable.docc/Tutorials/Helper/CommonStrategies.tutorial b/Sources/MetaCodable/MetaCodable.docc/Tutorials/Helper/CommonStrategies.tutorial index f83c680cd8..50629281ee 100644 --- a/Sources/MetaCodable/MetaCodable.docc/Tutorials/Helper/CommonStrategies.tutorial +++ b/Sources/MetaCodable/MetaCodable.docc/Tutorials/Helper/CommonStrategies.tutorial @@ -1,36 +1,36 @@ @Tutorial(time: 10) { @Intro(title: "Using Common Strategies") { Learn how to apply strategies like `ValueCoder` to all properties automatically. - + @Comment { Add image asset here! } } - + @Section(title: "Applying Common Strategies") { @ContentAndMedia { Learn how to use common strategies to automatically apply certain coding behaviors to all properties in a type, reducing the need for repetitive property annotations. - + @Image(source: "common-strategies", alt: "An illustration showing common strategies.") } - + @Steps { @Step { First, let's look at a model type without common strategies: - + @Code(name: "Product.swift", file: "Product-01.swift") } - + @Step { Now, let's use ValueCoder for all properties using commonStrategies: - + @Code(name: "Product.swift", file: "Product-02.swift") } - + @Step { Finally, let's see how we can override common strategies for specific properties: - + @Code(name: "Product.swift", file: "Product-03.swift") } } @@ -40,17 +40,17 @@ @ContentAndMedia { Learn about the different common strategies that can be applied to your types. } - + @Steps { @Step { The ValueCoder strategy can be applied to all properties: - + @Code(name: "User.swift", file: "CommonStrategies-01.swift") } - + @Step { You can also combine multiple strategies in future: - + @Code(name: "User.swift", file: "CommonStrategies-02.swift") } } @@ -59,28 +59,28 @@ @Assessments { @MultipleChoice { What is the benefit of using common strategies with the `@Codable` macro? - + @Choice(isCorrect: false) { It makes the code compile faster. - + @Justification(reaction: "Try again!") { While common strategies don't impact compile time significantly, they help reduce code repetition. } } - + @Choice(isCorrect: true) { It reduces the need to annotate each property individually with the same strategy. - + @Justification(reaction: "Correct!") { Common strategies allow you to apply the same coding behavior to all properties without repeating annotations. } } - + @Choice(isCorrect: false) { It improves runtime performance of coding operations. - + @Justification(reaction: "Try again!") { Common strategies don't affect runtime performance, they just make the code more maintainable by reducing repetition.