Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
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
2 changes: 1 addition & 1 deletion LICENSE.txt
Original file line number Diff line number Diff line change
@@ -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
Expand Down
4 changes: 0 additions & 4 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
6 changes: 3 additions & 3 deletions src/UtilityVerse.ASPNET/UtilityVerse.ASPNET.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netstandard2.1; net6.0; net8.0</TargetFrameworks>
<TargetFrameworks>netstandard2.1; net6.0; net8.0; net9.0</TargetFrameworks>
<LangVersion>Latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
Expand Down Expand Up @@ -52,8 +52,8 @@


<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" />
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.3.0" />
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.3.0" />
</ItemGroup>

</Project>
56 changes: 0 additions & 56 deletions src/UtilityVerse.Copy/Analyzers/MissingPartialErrorAnalyzer.cs

This file was deleted.

122 changes: 0 additions & 122 deletions src/UtilityVerse.Copy/Analyzers/NestedTypeErrorAnalyzer.cs

This file was deleted.

30 changes: 30 additions & 0 deletions src/UtilityVerse.Copy/Analyzers/Rules.cs
Original file line number Diff line number Diff line change
@@ -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);
}
133 changes: 133 additions & 0 deletions src/UtilityVerse.Copy/Analyzers/ValidationAnalyzer.cs
Original file line number Diff line number Diff line change
@@ -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<DiagnosticDescriptor> 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<ITypeSymbol>())
{
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;
}
}
Loading