diff --git a/LICENSE.txt b/LICENSE.txt index c85c401..12c11b7 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ MIT License -Copyright (c) [2023] [Pritom Purkayasta] +Copyright (c) [2025] [Pritom Purkayasta] Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/TODO.md b/TODO.md index 12e47d7..15f06ee 100644 --- a/TODO.md +++ b/TODO.md @@ -5,7 +5,3 @@ - [ ] Enum To String Converter function. - [ ] Mapper - [ ] Object Mapper Attribute with Source Code Generation. - - -- [ ] Rewrite the unit test and test only the core logic part. -- [ ] Upload into the nuget. \ No newline at end of file diff --git a/src/UtilityVerse.ASPNET/UtilityVerse.ASPNET.csproj b/src/UtilityVerse.ASPNET/UtilityVerse.ASPNET.csproj index bcfb76b..f16982c 100644 --- a/src/UtilityVerse.ASPNET/UtilityVerse.ASPNET.csproj +++ b/src/UtilityVerse.ASPNET/UtilityVerse.ASPNET.csproj @@ -1,7 +1,7 @@ ο»Ώ - netstandard2.1; net6.0; net8.0 + netstandard2.1; net6.0; net8.0; net9.0 Latest enable enable @@ -52,8 +52,8 @@ - - + + diff --git a/src/UtilityVerse.Copy/Analyzers/MissingPartialErrorAnalyzer.cs b/src/UtilityVerse.Copy/Analyzers/MissingPartialErrorAnalyzer.cs deleted file mode 100644 index 3161723..0000000 --- a/src/UtilityVerse.Copy/Analyzers/MissingPartialErrorAnalyzer.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System.Collections.Immutable; -using System.Linq; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; - -namespace UtilityVerse.Copy.Analyzers; - -[DiagnosticAnalyzer(LanguageNames.CSharp)] -public class MissingPartialErrorAnalyzer : DiagnosticAnalyzer -{ - private static readonly DiagnosticDescriptor MissingPartialModifierRule = new( - id: "UV001", - title: "Type must be marked as partial for DeepCopy/ShallowCopy generation", - messageFormat: "Type '{0}' must be marked as 'partial' to support DeepCopy/ShallowCopy generation", - category: "UtilityVerse.Copy", - DiagnosticSeverity.Error, - isEnabledByDefault: true, - description: "UtilityVerse.Copy requires all target types to be marked with 'partial' so the DeepCopy() or ShallowCopy() method can be injected." - ); - - public override ImmutableArray SupportedDiagnostics => [MissingPartialModifierRule]; - - public override void Initialize(AnalysisContext context) - { - // Configure analysis for syntax trees - context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); - context.EnableConcurrentExecution(); - - context.RegisterSyntaxNodeAction(AnalyzeTypeDeclaration, SyntaxKind.ClassDeclaration, SyntaxKind.StructDeclaration, SyntaxKind.RecordDeclaration, SyntaxKind.RecordStructDeclaration); - } - - private static void AnalyzeTypeDeclaration(SyntaxNodeAnalysisContext context) - { - var typeDecl = (TypeDeclarationSyntax)context.Node; - - // Skip if already marked partial - if (typeDecl.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword))) - return; - - // Skip if not user-defined (e.g., external/metadata references) - var symbol = context.SemanticModel.GetDeclaredSymbol(typeDecl); - if (symbol is null || symbol.DeclaringSyntaxReferences.Length == 0) - return; - - // Optionally: check if it's used for DeepCopy somehow - // Example: skip types with [GeneratedCode] or [CompilerGenerated] - if (symbol.GetAttributes().Any(attr => - attr.AttributeClass?.ToDisplayString() is "System.CodeDom.Compiler.GeneratedCodeAttribute" or "System.Runtime.CompilerServices.CompilerGeneratedAttribute")) - return; - - var diagnostic = Diagnostic.Create(MissingPartialModifierRule, typeDecl.Identifier.GetLocation(), symbol.Name); - context.ReportDiagnostic(diagnostic); - } -} \ No newline at end of file diff --git a/src/UtilityVerse.Copy/Analyzers/NestedTypeErrorAnalyzer.cs b/src/UtilityVerse.Copy/Analyzers/NestedTypeErrorAnalyzer.cs deleted file mode 100644 index 1c3a407..0000000 --- a/src/UtilityVerse.Copy/Analyzers/NestedTypeErrorAnalyzer.cs +++ /dev/null @@ -1,122 +0,0 @@ -using System.Collections.Immutable; -using System.Linq; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; - -namespace UtilityVerse.Copy.Analyzers; - -[DiagnosticAnalyzer(LanguageNames.CSharp)] -public class NestedTypeErrorAnalyzer : DiagnosticAnalyzer -{ - private static readonly DiagnosticDescriptor DeepRule = new( - id: "UV002", - title: "Nested type missing DeepCopy attribute", - messageFormat: "Property '{0}' of type '{1}' requires nested type '{2}' to have DeepCopy attribute or implement IDeepCopy", - category: "UtilityVerse.Copy", - defaultSeverity: DiagnosticSeverity.Error, - isEnabledByDefault: true); - - private static readonly DiagnosticDescriptor ShallowRule = new DiagnosticDescriptor( - id: "UV003", - title: "Nested type missing ShallowCopy attribute", - messageFormat: "Property '{0}' of type '{1}' requires nested type '{2}' to have ShallowCopy attribute or implement IShallowCopy", - category: "UtilityVerse.Copy", - defaultSeverity: DiagnosticSeverity.Error, - isEnabledByDefault: true); - - public override ImmutableArray SupportedDiagnostics => - [DeepRule, ShallowRule]; - - public override void Initialize(AnalysisContext context) - { - context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); - context.EnableConcurrentExecution(); - - context.RegisterSymbolAction(AnalyzeNamedType, SymbolKind.NamedType); - } - - private void AnalyzeNamedType(SymbolAnalysisContext context) - { - var namedType = (INamedTypeSymbol)context.Symbol; - - // Only analyze public partial types (assuming generator requires partial) - if (namedType.DeclaredAccessibility != Accessibility.Public) - return; - - if (!namedType.DeclaringSyntaxReferences.Any(syntaxRef => - syntaxRef.GetSyntax() is TypeDeclarationSyntax tds && - tds.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword)))) - return; - - // Check if type opts into DeepCopy or ShallowCopy - bool hasDeepCopy = Helper.HasDeepCopyOptIn(namedType); - bool hasShallowCopy = Helper.HasShallowCopyOptIn(namedType); - - if (!hasDeepCopy && !hasShallowCopy) - return; // type itself not opted in - no need to check nested types - - // Get all properties including inherited - var properties = namedType.GetMembers().OfType().Where(p => !p.IsStatic); - - foreach (var prop in properties) - { - if (prop.Type is not INamedTypeSymbol propType) - { - continue; - } - // Skip primitives and enums - if (Helper.IsTrulyPrimitive(propType)) - continue; - - if (propType.IsGenericType) - { - foreach (var typeArg in propType.TypeArguments.OfType()) - { - if (Helper.IsTrulyPrimitive(typeArg)) - continue; - - if (hasDeepCopy && !Helper.HasDeepCopyOptIn(typeArg)) - { - var diagnostic = Diagnostic.Create( - DeepRule, - prop.Locations.FirstOrDefault(), - prop.Name, namedType.Name, typeArg.Name); - context.ReportDiagnostic(diagnostic); - } - - if (hasShallowCopy && !Helper.HasShallowCopyOptIn(typeArg)) - { - var diagnostic = Diagnostic.Create( - ShallowRule, - prop.Locations.FirstOrDefault(), - prop.Name, namedType.Name, typeArg.Name); - context.ReportDiagnostic(diagnostic); - } - } - } - else - { - // Handle non-generic nested types - if (hasDeepCopy && !Helper.HasDeepCopyOptIn(propType)) - { - var diagnostic = Diagnostic.Create( - DeepRule, - prop.Locations.FirstOrDefault(), - prop.Name, namedType.Name, propType.Name); - context.ReportDiagnostic(diagnostic); - } - - if (hasShallowCopy && !Helper.HasShallowCopyOptIn(propType)) - { - var diagnostic = Diagnostic.Create( - ShallowRule, - prop.Locations.FirstOrDefault(), - prop.Name, namedType.Name, propType.Name); - context.ReportDiagnostic(diagnostic); - } - } - } - } -} \ No newline at end of file diff --git a/src/UtilityVerse.Copy/Analyzers/Rules.cs b/src/UtilityVerse.Copy/Analyzers/Rules.cs new file mode 100644 index 0000000..5bb93a9 --- /dev/null +++ b/src/UtilityVerse.Copy/Analyzers/Rules.cs @@ -0,0 +1,30 @@ +ο»Ώusing Microsoft.CodeAnalysis; + +namespace UtilityVerse.Copy.Analyzers; + +internal static class Rules +{ + internal static readonly DiagnosticDescriptor MissingPartialRule = new( + id: "UV001", + title: "Type must be marked as partial", + messageFormat: "Type '{0}' must be marked as 'partial' to support DeepCopy/ShallowCopy generation", + category: "UtilityVerse.Copy", + DiagnosticSeverity.Error, + isEnabledByDefault: true); + + internal static readonly DiagnosticDescriptor DeepRule = new( + id: "UV002", + title: "Nested type missing DeepCopy attribute", + messageFormat: "Property '{0}' of type '{1}' requires nested type '{2}' to have DeepCopy attribute or implement IDeepCopy", + category: "UtilityVerse.Copy", + DiagnosticSeverity.Error, + isEnabledByDefault: true); + + internal static readonly DiagnosticDescriptor ShallowRule = new( + id: "UV003", + title: "Nested type missing ShallowCopy attribute", + messageFormat: "Property '{0}' of type '{1}' requires nested type '{2}' to have ShallowCopy attribute or implement IShallowCopy", + category: "UtilityVerse.Copy", + DiagnosticSeverity.Error, + isEnabledByDefault: true); +} \ No newline at end of file diff --git a/src/UtilityVerse.Copy/Analyzers/ValidationAnalyzer.cs b/src/UtilityVerse.Copy/Analyzers/ValidationAnalyzer.cs new file mode 100644 index 0000000..6207a06 --- /dev/null +++ b/src/UtilityVerse.Copy/Analyzers/ValidationAnalyzer.cs @@ -0,0 +1,133 @@ +ο»Ώusing System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace UtilityVerse.Copy.Analyzers; + +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public class ValidationAnalyzer : DiagnosticAnalyzer +{ + public override ImmutableArray SupportedDiagnostics => + [Rules.MissingPartialRule, Rules.DeepRule, Rules.ShallowRule]; + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + + context.RegisterCompilationStartAction(compilationContext => + { + compilationContext.RegisterSymbolAction(AnalyzeNamedType, SymbolKind.NamedType); + }); + } + + private static void AnalyzeNamedType(SymbolAnalysisContext context) + { + if (context.Symbol is not INamedTypeSymbol namedType) + return; + + if (namedType.TypeKind is not (TypeKind.Class or TypeKind.Struct)) + return; + + if (namedType.GetAttributes().Any(attr => + attr.AttributeClass?.ToDisplayString() is + "System.CodeDom.Compiler.GeneratedCodeAttribute" or + "System.Runtime.CompilerServices.CompilerGeneratedAttribute")) + return; + + var syntaxRef = namedType.DeclaringSyntaxReferences.FirstOrDefault(); + if (syntaxRef?.GetSyntax() is not TypeDeclarationSyntax typeSyntax) + return; + + var hasDeepCopy = Helper.HasDeepCopyOptIn(namedType); + var hasShallowCopy = Helper.HasShallowCopyOptIn(namedType); + + if ((hasDeepCopy || hasShallowCopy) && !Helper.IsPartial(namedType)) + { + var diagnostic = Diagnostic.Create(Rules.MissingPartialRule, typeSyntax.Identifier.GetLocation(), namedType.Name); + context.ReportDiagnostic(diagnostic); + } + + if (!hasDeepCopy && !hasShallowCopy) + return; + + foreach (var prop in Helper.GetAllProperties(namedType).Where(p => !p.IsStatic)) + { + AnalyzeTypeRecursive( + context, + prop.Type, + prop.Locations.FirstOrDefault() ?? typeSyntax.Identifier.GetLocation(), + prop.Name, + namedType, + hasDeepCopy, + hasShallowCopy + ); + } + } + + private static void AnalyzeTypeRecursive( + SymbolAnalysisContext context, + ITypeSymbol type, + Location diagnosticLocation, + string propertyName, + INamedTypeSymbol parentType, + bool requireDeepCopy, + bool requireShallowCopy) + { + if (type is not INamedTypeSymbol namedType) + return; + + var unwrapped = UnwrapNullable(namedType); + + if (Helper.IsTrulyPrimitive(unwrapped)) + return; + + if (Helper.IsGenericCollection(unwrapped.OriginalDefinition.ToDisplayString(), out _)) + { + foreach (var typeArg in unwrapped.TypeArguments.OfType()) + { + AnalyzeTypeRecursive(context, typeArg, diagnosticLocation, propertyName, parentType, requireDeepCopy, requireShallowCopy); + } + return; + } + + if (requireDeepCopy && !Helper.HasDeepCopyOptIn(unwrapped)) + { + var diag = Diagnostic.Create(Rules.DeepRule, diagnosticLocation, + propertyName, parentType.Name, unwrapped.Name); + context.ReportDiagnostic(diag); + } + + if (requireShallowCopy && !Helper.HasShallowCopyOptIn(unwrapped)) + { + var diag = Diagnostic.Create(Rules.ShallowRule, diagnosticLocation, + propertyName, parentType.Name, unwrapped.Name); + context.ReportDiagnostic(diag); + } + } + + private static INamedTypeSymbol UnwrapNullable(INamedTypeSymbol type) + { + return type.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T && + type.TypeArguments.FirstOrDefault() is INamedTypeSymbol underlying + ? underlying + : type; + } + + private static bool IsInsideCopyEnabledPartialParent(INamedTypeSymbol? symbol) + { + while (symbol is not null) + { + if (!Helper.IsPartial(symbol)) + return false; + + if (Helper.HasDeepCopyOptIn(symbol) || Helper.HasShallowCopyOptIn(symbol)) + return true; + + symbol = symbol.ContainingType; + } + return false; + } +} diff --git a/src/UtilityVerse.Copy/Helper.cs b/src/UtilityVerse.Copy/Helper.cs index 7a0a838..83ce90d 100644 --- a/src/UtilityVerse.Copy/Helper.cs +++ b/src/UtilityVerse.Copy/Helper.cs @@ -1,9 +1,3 @@ -/// -/// Author: Pritom Purkayasta -// Copyright (c) Pritom Purkayasta All rights reserved. -// FREE TO USE TO CONNECT THE WORLD -/// - using System.Collections.Generic; using System.Linq; using Microsoft.CodeAnalysis; @@ -22,19 +16,19 @@ internal static bool IsTrulyPrimitive(ITypeSymbol symbol) return symbol.SpecialType switch { SpecialType.System_Boolean or - SpecialType.System_Byte or - SpecialType.System_SByte or - SpecialType.System_Int16 or - SpecialType.System_UInt16 or - SpecialType.System_Int32 or - SpecialType.System_UInt32 or - SpecialType.System_Int64 or - SpecialType.System_UInt64 or - SpecialType.System_Single or - SpecialType.System_Double or - SpecialType.System_Char or - SpecialType.System_Enum or - SpecialType.System_String => true, + SpecialType.System_Byte or + SpecialType.System_SByte or + SpecialType.System_Int16 or + SpecialType.System_UInt16 or + SpecialType.System_Int32 or + SpecialType.System_UInt32 or + SpecialType.System_Int64 or + SpecialType.System_UInt64 or + SpecialType.System_Single or + SpecialType.System_Double or + SpecialType.System_Char or + SpecialType.System_Enum or + SpecialType.System_String => true, _ => false }; } @@ -43,7 +37,6 @@ internal static bool IsGenericCollection(string? originalDef, out string? kind) { kind = originalDef switch { - // List-like collections "System.Collections.Generic.List" => "List", "System.Collections.Generic.IList" => "List", "System.Collections.ObjectModel.Collection" => "List", @@ -51,30 +44,25 @@ internal static bool IsGenericCollection(string? originalDef, out string? kind) "System.Collections.ObjectModel.ObservableCollection" => "List", "System.ComponentModel.BindingList" => "List", "System.Collections.Immutable.ImmutableList" => "List", - "System.Collections.Generic.ReadOnlyList" => "List", // custom/unofficial + "System.Collections.Generic.ReadOnlyList" => "List", - // Enumerable-like collections "System.Collections.Generic.IEnumerable" => "Enumerable", "System.Collections.Generic.ICollection" => "Enumerable", "System.Collections.Generic.IReadOnlyCollection" => "Enumerable", "System.Collections.Generic.IReadOnlyList" => "Enumerable", - // Hash-based collections "System.Collections.Generic.HashSet" => "HashSet", - // Dictionary-like collections "System.Collections.Generic.Dictionary" => "Dictionary", "System.Collections.Frozen.FrozenDictionary" => "Dictionary", "System.Collections.Concurrent.ConcurrentDictionary" => "Dictionary", - // Immutable "System.Collections.Immutable.ImmutableArray" => "List", "System.Collections.Immutable.ImmutableSortedSet" => "HashSet", "System.Collections.Immutable.ImmutableHashSet" => "HashSet", "System.Collections.Immutable.ImmutableDictionary" => "Dictionary", "System.Collections.Immutable.ImmutableSortedDictionary" => "Dictionary", - // Queue / Stack "System.Collections.Generic.Queue" => "Queue", "System.Collections.Generic.Stack" => "Stack", @@ -107,13 +95,22 @@ internal static bool IsPartial(INamedTypeSymbol typeSymbol) internal static bool HasDeepCopyOptIn(INamedTypeSymbol typeSymbol) { - return typeSymbol.GetAttributes().Any(a => a.AttributeClass?.ToDisplayString() == "UtilityVerse.Copy.DeepCopy") || - typeSymbol.AllInterfaces.Any(i => i.ToDisplayString() == "UtilityVerse.Copy.IDeepCopy"); + return typeSymbol.GetAttributes().Any(a => + SymbolEquals(a.AttributeClass, "UtilityVerse.Copy.DeepCopy")) || + typeSymbol.AllInterfaces.Any(i => + SymbolEquals(i, "UtilityVerse.Copy.IDeepCopy")); } internal static bool HasShallowCopyOptIn(INamedTypeSymbol typeSymbol) { - return typeSymbol.GetAttributes().Any(a => a.AttributeClass?.ToDisplayString() == "UtilityVerse.Copy.ShallowCopy") || - typeSymbol.AllInterfaces.Any(i => i.ToDisplayString() == "UtilityVerse.Copy.IShallowCopy"); + return typeSymbol.GetAttributes().Any(a => + SymbolEquals(a.AttributeClass, "UtilityVerse.Copy.ShallowCopy")) || + typeSymbol.AllInterfaces.Any(i => + SymbolEquals(i, "UtilityVerse.Copy.IShallowCopy")); + } + + private static bool SymbolEquals(ISymbol? symbol, string fullyQualifiedMetadataName) + { + return symbol?.ToDisplayString() == fullyQualifiedMetadataName; } -} \ No newline at end of file +} diff --git a/src/UtilityVerse.Copy/README.md b/src/UtilityVerse.Copy/README.md index c3c24e7..b9fb7b2 100644 --- a/src/UtilityVerse.Copy/README.md +++ b/src/UtilityVerse.Copy/README.md @@ -1,174 +1,183 @@ + --- # πŸ“¦ UtilityVerse.Copy β€” Source Generator for DeepCopy and ShallowCopy in C\# -πŸš€ **UtilityVerse.Copy** is a Roslyn-based source generator that automatically creates `DeepCopy()` and `ShallowCopy()` methods for your models. Eliminate repetitive boilerplate and enjoy clean, maintainable code with zero runtime dependencies. - +![Nuget](https://img.shields.io/nuget/v/UtilityVerse.Copy) ![Nuget](https://img.shields.io/nuget/dt/UtilityVerse.Copy?style=plastic) --- -## ✨ Features +## πŸ“– What is UtilityVerse.Copy? -* βœ… Automatically generates `DeepCopy()` and/or `ShallowCopy()` methods at compile-time -* βœ… No runtime overhead β€” it's all generated in the background by Roslyn -* βœ… Supports both `[Attribute]`-based and `interface`-based opt-in mechanisms -* βœ… Works great for DTOs, ViewModels, and plain C# objects -* βœ… Support for common collection types, arrays, tuples, and more +**UtilityVerse.Copy** is a Roslyn-based source generator for C#. It automatically creates `DeepCopy()` and `ShallowCopy()` methods for your classes and structs at compile-time. With zero runtime dependencies, your copy logic is safe, efficient, and maintainable β€” without writing repetitive code. --- +## ❓ Why Use It? -## > Give it a star if you like the project. πŸ‘ 🌠 🌟 +* Copying objects (deep or shallow) usually requires verbose, error-prone boilerplate. +* UtilityVerse.Copy removes that burden by generating fully-typed copy methods at compile-time using Roslyn. +* No runtime reflection, no magic β€” just pure, reliable, compiled code. +* Perfect for: + * DTOs + * ViewModels + * Configuration models + * Game entities + * Domain models needing clean clone operations --- -## πŸ“¦ Installation +## ✨ Features -Install the NuGet package: +* βœ… Auto-generates `DeepCopy()` and/or `ShallowCopy()` methods +* βœ… Pure compile-time code generation (no runtime dependencies) +* βœ… Supports both `[Attributes]` and `Marker Interfaces` +* βœ… Handles: -```bash -dotnet add package UtilityVerse.Copy -``` + * Common collections (List, HashSet, Dictionary, etc.) + * Arrays, Tuples, ValueTuples + * Primitive types, strings, and nested objects + * Records, classes, and structs (as long as they are `partial`) +* βœ… Safe β€” generated code is separate and non-intrusive --- -## πŸ› οΈ How It Works +## πŸ”‘ API β€” What You Need to Use -> **Important**: Your classes must be marked `partial` for the generator to emit code successfully. +### 1️⃣ Attributes -### βœ… Option 1: Use Attributes +Decorate your classes/structs using: -Apply `[ShallowCopy]` or `[DeepCopy]` to your class or struct: +* `[DeepCopy]` β€” to generate a recursive deep copy method. +* `[ShallowCopy]` β€” to generate a simple shallow copy. + +Example: ```csharp using UtilityVerse.Copy; -[ShallowCopy] -public partial class Person +[DeepCopy] +public partial class Product { public string Name { get; set; } - public int Age { get; set; } + public decimal Price { get; set; } } ``` -This generates a `ShallowCopy()` method at compile-time: +--- -```csharp -public Person ShallowCopy() -{ - return (Person)this.MemberWiseClone(); -} -``` +### 2️⃣ Marker Interfaces -For deep copy: +If you prefer code-first opt-in: + +* Implement `IDeepCopy` for deep copy generation. +* Implement `IShallowCopy` for shallow copy generation. + +Example: ```csharp using UtilityVerse.Copy; -[DeepCopy] -public partial class Person + +public partial class Product : IDeepCopy { public string Name { get; set; } - public int Age { get; set; } + public decimal Price { get; set; } } ``` -Generates a deep recursive `DeepCopy()` method that copies nested references and collections. +--- -```csharp -public Person DeepCopy() -{ - return new Person - { - Name = this.Name, - Age = this.Age - }; -} -``` +> **Important:** +> Your class or struct **must be marked `partial`** for the source generator to work. --- -### βœ… Option 2: Use Marker Interfaces +## πŸš€ Tutorial β€” How to Use -Prefer no attributes? Just implement the marker interfaces: +### Step 1️⃣ β€” Install via NuGet + +```bash +dotnet add package UtilityVerse.Copy +``` + +--- + +### Step 2️⃣ β€” Add `[DeepCopy]` or `[ShallowCopy]` ```csharp using UtilityVerse.Copy; -public partial class Person : IShallowCopy + +[DeepCopy] +public partial class User { - public string Name { get; set; } - public int Age { get; set; } + public int Id { get; set; } + public string? Name { get; set; } + public List
Addresses { get; set; } = []; } -``` -```csharp -using UtilityVerse.Copy; -public partial class Person : IDeepCopy +[DeepCopy] +public partial class Address { - public string Name { get; set; } - public int Age { get; set; } + public string? StreetNumber { get; set; } } ``` -The generator will handle the rest automatically! - --- -## πŸ“š Supported Types +### Step 3️⃣ β€” Generated Code (Example) -* βœ… Primitives and strings -* βœ… Arrays -* βœ… Tuples and `ValueTuple` -* βœ… Generic collections: `List`, `IEnumerable`, `ICollection`, `IReadOnlyList`, `HashSet`, etc. -* βœ… Dictionary-like collections: `Dictionary`, `ConcurrentDictionary`, `FrozenDictionary`, etc. -* βœ… Record types, classes, structs (as long as they are `partial`) -* βœ… Nested properties recursively copied in `DeepCopy()` - ---- - -## πŸ§ͺ Sample Output - -Given this class: +`DeepCopy()` for `User` will be automatically generated: ```csharp -[DeepCopy] -public partial class User +public User DeepCopy() { - public string Name { get; set; } - public List
Addresses { get; set; } + return new User + { + Id = this.Id, + Name = this.Name, + Addresses = this.Addresses?.Select(x => x?.DeepCopy()).ToList() + }; } ``` -Generated deep copy: +Similarly for `Address`: ```csharp -public User DeepCopy() +public Address DeepCopy() { - return new User + return new Address { - Name = this.Name, - Addresses = this.Addresses?.Select(x => x?.DeepCopy()).ToList() + StreetNumber = this.StreetNumber }; } ``` --- -## πŸ”’ Safe and Reliable +### Step 4️⃣ β€” Use It! -* πŸ’‘ **Partial**: Won’t overwrite your code β€” generated code lives alongside your class -* 🧾 **Readable**: Generated files are emitted to the intermediate folder (obj) -* βš™οΈ **Non-Intrusive**: No reflection, no extra dependencies +```csharp +var userClone = existingUser.DeepCopy(); +``` --- -## πŸ“„ License +## πŸ“š Supported Types -This project is licensed under the [MIT License](LICENSE). +* βœ… Primitives (`int`, `string`, `bool`, etc.) +* βœ… Arrays +* βœ… Tuples & ValueTuples +* βœ… Generic collections (`List`, `Dictionary`, etc.) +* βœ… Records, classes, structs (`partial` required) +* βœ… Deep recursive copying of nested properties --- -## 🀝 Contributing -We welcome contributions! Please see the [CONTRIBUTE.md](CONTRIBUTE.md) file for guidelines. +## πŸ“£ Like It? Star It! ⭐ + +If you find UtilityVerse.Copy useful, give the repository a ⭐ and help others discover it! + +--- ---- \ No newline at end of file +Let me know if you'd like this structured as a template file or want me to generate sample output code files directly. diff --git a/src/UtilityVerse.Copy/UtilityVerse.Copy.csproj b/src/UtilityVerse.Copy/UtilityVerse.Copy.csproj index 3e5aee4..ad62653 100644 --- a/src/UtilityVerse.Copy/UtilityVerse.Copy.csproj +++ b/src/UtilityVerse.Copy/UtilityVerse.Copy.csproj @@ -15,7 +15,7 @@ Copy UtilityVerse.Copy - 0.3.0 + 0.4.0 Pritom Purkayasta A Roslyn source generator for generating shallow copy and deep copy for class, records and structs diff --git a/src/UtilityVerse.Copy/UtilityVerse.Copy.sln b/src/UtilityVerse.Copy/UtilityVerse.Copy.sln deleted file mode 100644 index 5f63515..0000000 --- a/src/UtilityVerse.Copy/UtilityVerse.Copy.sln +++ /dev/null @@ -1,24 +0,0 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.5.2.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UtilityVerse.Copy", "UtilityVerse.Copy.csproj", "{381427B0-86D7-C4BF-ADDE-F56BD66B638B}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {381427B0-86D7-C4BF-ADDE-F56BD66B638B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {381427B0-86D7-C4BF-ADDE-F56BD66B638B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {381427B0-86D7-C4BF-ADDE-F56BD66B638B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {381427B0-86D7-C4BF-ADDE-F56BD66B638B}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {54E540C6-9E0C-4569-B917-30077F35B028} - EndGlobalSection -EndGlobal diff --git a/src/UtilityVerse.Copy/nuget.md b/src/UtilityVerse.Copy/nuget.md index 21e42f2..e84e0bb 100644 --- a/src/UtilityVerse.Copy/nuget.md +++ b/src/UtilityVerse.Copy/nuget.md @@ -17,146 +17,5 @@ --- -## > Give it a star if you like the project. πŸ‘ 🌠 🌟 - ---- - -## πŸ“¦ Installation - -Install the NuGet package: - -```bash -dotnet add package UtilityVerse.Copy -``` - ---- - -## πŸ› οΈ How It Works - -> **Important**: Your classes must be marked `partial` for the generator to emit code successfully. - -### βœ… Option 1: Use Attributes - -Apply `[ShallowCopy]` or `[DeepCopy]` to your class or struct: - -```csharp -using UtilityVerse.Copy; - -[ShallowCopy] -public partial class Person -{ - public string Name { get; set; } - public int Age { get; set; } -} -``` - -This generates a `ShallowCopy()` method at compile-time: - -```csharp -public Person ShallowCopy() -{ - return (Person)this.MemberWiseClone(); -} -``` - -For deep copy: - -```csharp -using UtilityVerse.Copy; -[DeepCopy] -public partial class Person -{ - public string Name { get; set; } - public int Age { get; set; } -} -``` - -Generates a deep recursive `DeepCopy()` method that copies nested references and collections. - -```csharp -public Person DeepCopy() -{ - return new Person - { - Name = this.Name, - Age = this.Age - }; -} -``` - ---- - -### βœ… Option 2: Use Marker Interfaces - -Prefer no attributes? Just implement the marker interfaces: - -```csharp -using UtilityVerse.Copy; -public partial class Person : IShallowCopy -{ - public string Name { get; set; } - public int Age { get; set; } -} -``` - -```csharp -using UtilityVerse.Copy; -public partial class Person : IDeepCopy -{ - public string Name { get; set; } - public int Age { get; set; } -} -``` - -The generator will handle the rest automatically! - ---- - -## πŸ“š Supported Types - -* βœ… Primitives and strings -* βœ… Arrays -* βœ… Tuples and `ValueTuple` -* βœ… Generic collections: `List`, `IEnumerable`, `ICollection`, `IReadOnlyList`, `HashSet`, etc. -* βœ… Dictionary-like collections: `Dictionary`, `ConcurrentDictionary`, `FrozenDictionary`, etc. -* βœ… Record types, classes, structs (as long as they are `partial`) -* βœ… Nested properties recursively copied in `DeepCopy()` - ---- - -## πŸ§ͺ Sample Output - -Given this class: - -```csharp -[DeepCopy] -public partial class User -{ - public string Name { get; set; } - public List
Addresses { get; set; } -} -``` - -Generated deep copy: - -```csharp -public User DeepCopy() -{ - return new User - { - Name = this.Name, - Addresses = this.Addresses?.Select(x => x?.DeepCopy()).ToList() - }; -} -``` - ---- - -## πŸ”’ Safe and Reliable - -* πŸ’‘ **Partial**: Won’t overwrite your code β€” generated code lives alongside your class -* 🧾 **Readable**: Generated files are emitted to the intermediate folder (obj) -* βš™οΈ **Non-Intrusive**: No reflection, no extra dependencies - ---- \ No newline at end of file +> Code is fully opensource and more details can be found here at: [Github](https://github.com/purkayasta/TheUtilityVerse/blob/development/src/UtilityVerse.Copy/README.md) \ No newline at end of file diff --git a/src/UtilityVerse/breaking-changes.md b/src/UtilityVerse/breaking-changes.md index f561dd3..bd1d520 100644 --- a/src/UtilityVerse/breaking-changes.md +++ b/src/UtilityVerse/breaking-changes.md @@ -1,5 +1,4 @@ # Breaking Changes -- [x] Upgraded to .net8.0 and .netstandard2.1 -- [x] Instead of throwing exception, returning a data type where errors are given in a text so that developer can log it and handle their own way. -- [x] Remove Nullability. \ No newline at end of file +- [x] Upgraded to .net9.0 and .netstandard2.1 +- [x] Instead of throwing exception, returning a data type where errors are given in a text so that developer can log it and handle their own way. \ No newline at end of file diff --git a/test/UtilityVerse.UnitTest/UtilityVerse.UnitTest.csproj b/test/UtilityVerse.UnitTest/UtilityVerse.UnitTest.csproj index d863d7b..6bc15f5 100644 --- a/test/UtilityVerse.UnitTest/UtilityVerse.UnitTest.csproj +++ b/test/UtilityVerse.UnitTest/UtilityVerse.UnitTest.csproj @@ -9,13 +9,13 @@ - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all