Skip to content

Commit 622c052

Browse files
committed
Add AutoDetails feature for generating detail classes
Updated Directory.Build.props to upgrade ExtSrcGenVer to 0.1.10. Introduced AutoDetailsGenerator for creating detail classes from properties with AutoDetailsAttribute. Enhanced AutoDetailsInformation with constructors, equality, and hashing methods. Modified AutoPropertiesGenerator to include AutoDetailsAssignDescriptorResolver and refactored property handling logic. Added AutoDetailsAssignDescriptorResolver class. Updated AutoPropertyOriginPropertiesRetriever for efficiency. Extended AutoPropertiesInformation to handle autoDetails. Updated AutoSelectInformation for direct generation. Added AutoDetailsTests1 to verify new feature functionality.
1 parent e3b1729 commit 622c052

7 files changed

Lines changed: 407 additions & 25 deletions

File tree

src/Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
</PropertyGroup>
77
<PropertyGroup>
88
<DotNetCoreVersion>8.0.20</DotNetCoreVersion>
9-
<ExtSrcGenVer>0.1.8</ExtSrcGenVer>
9+
<ExtSrcGenVer>0.1.10</ExtSrcGenVer>
1010
</PropertyGroup>
1111
<PropertyGroup>
1212
<Ver>0.2.0</Ver>
Lines changed: 112 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,117 @@
1-
using System;
2-
using System.Collections.Generic;
3-
using System.Text;
1+
using Microsoft.CodeAnalysis;
42

53
namespace RoyalCode.SmartSelector.Generators.Generators;
64

7-
internal class AutoDetailsGenerator
5+
internal static class AutoDetailsGenerator
86
{
7+
private const string AutoDetailsAttributeName = "AutoDetailsAttribute";
8+
9+
internal static bool TryCreate(
10+
PropertyDescriptor property,
11+
TypeDescriptor fromType,
12+
out AutoDetailsInformation? autoDetailInfo)
13+
{
14+
autoDetailInfo = null;
15+
16+
// verify if the property has AutoDetails attribute
17+
var autoDetailsAttribute = property.Symbol?.GetAttributes().FirstOrDefault(attr =>
18+
{
19+
var attrName = attr.AttributeClass?.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat);
20+
return attrName == AutoDetailsAttributeName;
21+
});
22+
23+
if (autoDetailsAttribute is null)
24+
return false;
25+
26+
// get the property of the fromType that matches the property name
27+
var fromProperty = fromType.CreateProperties(p => p.Name == property.Name && p.GetMethod is not null).FirstOrDefault();
28+
29+
// if not found, return error diagnostic
30+
if (fromProperty is null)
31+
{
32+
autoDetailInfo = new AutoDetailsInformation(
33+
Diagnostic.Create(AnalyzerDiagnostics.PropertyNotMatch, null, property.Name));
34+
return true;
35+
}
36+
37+
// get type descriptors
38+
var propertyType = property.Type;
39+
var fromPropertyType = fromProperty.Type;
40+
41+
// create auto properties information
42+
var autoPropertiesInfo = AutoPropertiesGenerator.CreateInformation(propertyType, fromPropertyType, autoDetailsAttribute);
43+
44+
// try update the namespace for the generated type with same namespaces of the property declaring type.
45+
var propertyNamespace = property.Symbol?.ContainingNamespace?.ToDisplayString();
46+
if (propertyNamespace is not null)
47+
propertyType.Namespaces[0] = propertyNamespace;
48+
49+
// set the defined properties for the auto detail info
50+
propertyType.DefinedProperties = autoPropertiesInfo.Properties;
51+
52+
// create the auto detail info
53+
autoDetailInfo = new AutoDetailsInformation(
54+
$"{fromPropertyType.Name}Details",
55+
autoPropertiesInfo);
56+
57+
return true;
58+
}
59+
60+
internal static void Generate(
61+
SourceProductionContext context,
62+
string className,
63+
AutoPropertiesInformation autoPropertiesInformation)
64+
{
65+
// Gera uma nova classe DTO contendo as propriedades de autoPropertiesInformation.
66+
// Segue o mesmo estilo de geração usado por AutoPropertiesGenerator.Generate.
67+
68+
var originType = autoPropertiesInformation.OriginType;
69+
var properties = autoPropertiesInformation.Properties;
70+
71+
// Se não há propriedades, não gera nada.
72+
if (originType == null || properties.Length == 0)
73+
return;
74+
75+
// 1 - Criação da classe
76+
var detailsClass = new ClassGenerator(className, originType.Namespaces[0]);
77+
78+
79+
// 1.1 - Modificadores (usa a mesma acessibilidade do tipo de origem)
80+
if (originType.Symbol?.DeclaredAccessibility == Accessibility.Public)
81+
{
82+
detailsClass.Modifiers.Public();
83+
}
84+
else if (originType.Symbol?.DeclaredAccessibility == Accessibility.Internal)
85+
{
86+
detailsClass.Modifiers.Internal();
87+
}
88+
else if (originType.Symbol?.DeclaredAccessibility == Accessibility.Protected)
89+
{
90+
detailsClass.Modifiers.Protected();
91+
}
92+
else if (originType.Symbol?.DeclaredAccessibility == Accessibility.Private)
93+
{
94+
detailsClass.Modifiers.Private();
95+
}
96+
97+
// 1.2 - partial, para o desenvolvedor poder estender
98+
detailsClass.Modifiers.Public();
99+
100+
// 2 - Criação das propriedades públicas com get/set
101+
foreach (var p in properties)
102+
{
103+
var propertyType = p.Type.HasNamedTypeSymbol(out var typeSymbol)
104+
? TypeDescriptor.Create(typeSymbol)
105+
: p.Type;
106+
107+
var prop = new PropertyGenerator(propertyType, p.Name);
108+
prop.Modifiers.Public();
109+
110+
detailsClass.Properties.Add(prop);
111+
}
112+
113+
// 3 - Configura o nome do arquivo e gera
114+
detailsClass.FileName = $"{className}.AutoDetails.g.cs";
115+
detailsClass.Generate(context);
116+
}
9117
}

src/RoyalCode.SmartSelector.Generators/Generators/AutoDetailsInformation.cs

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,61 @@ internal class AutoDetailsInformation : IEquatable<AutoDetailsInformation>
99
private readonly string? detailsClassName;
1010
private readonly AutoPropertiesInformation? autoPropertiesInformation;
1111

12+
public AutoDetailsInformation(Diagnostic diagnostic)
13+
{
14+
diagnostics = [diagnostic];
15+
}
16+
17+
public AutoDetailsInformation(
18+
string detailsClassName,
19+
AutoPropertiesInformation autoPropertiesInformation)
20+
{
21+
this.detailsClassName = detailsClassName;
22+
this.autoPropertiesInformation = autoPropertiesInformation;
23+
}
24+
1225
public bool Equals(AutoDetailsInformation other)
1326
{
14-
throw new NotImplementedException();
27+
if (other == null)
28+
{
29+
return false;
30+
}
31+
if (ReferenceEquals(this, other))
32+
{
33+
return true;
34+
}
35+
return diagnostics?.SequenceEqual(other.diagnostics) == true &&
36+
detailsClassName == other.detailsClassName &&
37+
Equals(autoPropertiesInformation, other.autoPropertiesInformation);
38+
}
39+
40+
public override bool Equals(object obj)
41+
{
42+
return obj is AutoDetailsInformation other && Equals(other);
43+
}
44+
45+
public override int GetHashCode()
46+
{
47+
var hashCode = 2147442367;
48+
hashCode = (hashCode * -1521134295) + (diagnostics != null ? diagnostics.GetHashCode() : 0);
49+
hashCode = (hashCode * -1521134295) + (detailsClassName != null ? detailsClassName.GetHashCode() : 0);
50+
hashCode = (hashCode * -1521134295) + (autoPropertiesInformation != null ? autoPropertiesInformation.GetHashCode() : 0);
51+
return hashCode;
52+
}
53+
54+
internal void Generate(SourceProductionContext context)
55+
{
56+
if (diagnostics is not null)
57+
{
58+
foreach (var diagnostic in diagnostics)
59+
{
60+
context.ReportDiagnostic(diagnostic);
61+
}
62+
}
63+
64+
if (autoPropertiesInformation is not null)
65+
{
66+
AutoDetailsGenerator.Generate(context, detailsClassName!, autoPropertiesInformation!);
67+
}
1568
}
1669
}

src/RoyalCode.SmartSelector.Generators/Generators/AutoPropertiesGenerator.cs

Lines changed: 89 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using Microsoft.CodeAnalysis;
22
using Microsoft.CodeAnalysis.CSharp;
33
using Microsoft.CodeAnalysis.CSharp.Syntax;
4-
using System.Text;
54

65
namespace RoyalCode.SmartSelector.Generators.Generators;
76

@@ -15,6 +14,7 @@ internal static class AutoPropertiesGenerator
1514
internal static MatchOptions MatchOptions { get; } = new()
1615
{
1716
OriginPropertiesRetriever = new AutoPropertyOriginPropertiesRetriever(),
17+
AdditionalAssignDescriptorResolvers = [new AutoDetailsAssignDescriptorResolver()],
1818
};
1919

2020
internal static bool Predicate(SyntaxNode node, CancellationToken token)
@@ -135,25 +135,37 @@ internal static AutoPropertiesInformation CreateInformation(
135135
TypeDescriptor modelType,
136136
ITypeSymbol fromType,
137137
AttributeData autoPropertyAttribute)
138+
{
139+
// gera o TypeDescriptor do fromType
140+
var fromTypeDescriptor = TypeDescriptor.Create(fromType);
141+
142+
return CreateInformation(modelType, fromTypeDescriptor, autoPropertyAttribute);
143+
}
144+
145+
internal static AutoPropertiesInformation CreateInformation(
146+
TypeDescriptor modelType,
147+
TypeDescriptor fromType,
148+
AttributeData autoPropertyAttribute)
138149
{
139150
var excluded = new HashSet<string>(StringComparer.Ordinal);
140151
var flattening = new HashSet<string>(StringComparer.Ordinal);
141152

142153
// obtém Exclude de NamedArguments
143154
foreach (var namedArg in autoPropertyAttribute.NamedArguments)
144155
if (namedArg.Key == "Exclude" && namedArg.Value.Kind == TypedConstantKind.Array && namedArg.Value.Values != null)
156+
{
145157
foreach (var v in namedArg.Value.Values)
146158
if (v.Value is string s)
147159
excluded.Add(s);
160+
}
148161
else if (namedArg.Key == "Flattening" && namedArg.Value.Kind == TypedConstantKind.Array && namedArg.Value.Values != null)
149-
foreach (var fv in namedArg.Value.Values)
150-
if (fv.Value is string fs)
151-
flattening.Add(fs);
152-
153-
// gera o TypeDescriptor do fromType
154-
var fromTypeDescriptor = TypeDescriptor.Create(fromType);
162+
{
163+
foreach (var fv in namedArg.Value.Values)
164+
if (fv.Value is string fs)
165+
flattening.Add(fs);
166+
}
155167

156-
return CreateInformation(modelType, fromTypeDescriptor, excluded, flattening);
168+
return CreateInformation(modelType, fromType, excluded, flattening);
157169
}
158170

159171
internal static AutoPropertiesInformation CreateInformation(
@@ -162,10 +174,19 @@ internal static AutoPropertiesInformation CreateInformation(
162174
HashSet<string> excluded,
163175
HashSet<string>? flattening)
164176
{
177+
var autoDetails = new List<AutoDetailsInformation>();
178+
165179
// declared properties in model type are always excluded
166180
foreach (var p in modelType.CreateProperties(p => p.SetMethod is not null))
181+
{
167182
excluded.Add(p.Name);
168183

184+
// processa se propriedade se tem atributo AutoDetails
185+
if(AutoDetailsGenerator.TryCreate(p, fromType, out var autoDetailInfo))
186+
{
187+
autoDetails.Add(autoDetailInfo!);
188+
}
189+
}
169190
var sourceProps = fromType.CreateProperties(p => p.GetMethod is not null);
170191

171192
// filtra propriedades do source,
@@ -192,7 +213,7 @@ internal static AutoPropertiesInformation CreateInformation(
192213
generated.Add(new PropertyDescriptor(p.Type, p.Name, p.Symbol));
193214
}
194215

195-
return new AutoPropertiesInformation(modelType, [.. generated]);
216+
return new AutoPropertiesInformation(modelType, [.. generated], [.. autoDetails]);
196217
}
197218

198219
private static IReadOnlyList<PropertyDescriptor> CreateFlattening(
@@ -339,8 +360,6 @@ internal static void Generate(AutoPropertiesInformation propertiesInfo, SourcePr
339360
partialClass.FileName = $"{origin.Name}.AutoProperties.g.cs";
340361
partialClass.Generate(context);
341362
}
342-
343-
344363
}
345364

346365
internal class AutoPropertyOriginPropertiesRetriever : IOriginPropertiesRetriever
@@ -351,9 +370,12 @@ internal class AutoPropertyOriginPropertiesRetriever : IOriginPropertiesRetrieve
351370

352371
public IReadOnlyList<PropertyDescriptor> GetProperties(TypeDescriptor origin)
353372
{
373+
var originProperties = MatchOptions.GetOriginProperties(origin);
374+
origin.DefinedProperties = originProperties;
354375
var typeSymbol = origin.Symbol;
376+
355377
if (typeSymbol == null)
356-
return MatchOptions.GetOriginProperties(origin);
378+
return originProperties;
357379

358380
// Verifica se no type existe:
359381
// - o AutoPropertiesAttribute com AutoSelectAttribute<TFrom>
@@ -378,18 +400,18 @@ public IReadOnlyList<PropertyDescriptor> GetProperties(TypeDescriptor origin)
378400

379401
// se não tiver, retorna o padrão
380402
if (autoSelectAttribute == null)
381-
return MatchOptions.GetOriginProperties(origin);
403+
return originProperties;
382404

383405
// obtém o tipo TFrom
384406
var fromType = autoSelectAttribute.AttributeClass?.TypeArguments.FirstOrDefault();
385407
if (fromType == null)
386-
return MatchOptions.GetOriginProperties(origin);
408+
return originProperties;
387409

388410
// cria a informação
389411
var info = AutoPropertiesGenerator.CreateInformation(origin, fromType, autoSelectAttribute);
390412

391413
// pega as propriedades da origem mais as da informação
392-
return [.. MatchOptions.GetOriginProperties(origin), .. info.Properties];
414+
return [.. originProperties, .. info.Properties];
393415
}
394416

395417
// obtém o AutoPropertiesAttribute<TFrom>
@@ -404,15 +426,64 @@ public IReadOnlyList<PropertyDescriptor> GetProperties(TypeDescriptor origin)
404426
// obtém o tipo TFrom
405427
var fromType = autoPropertiesAttribute.AttributeClass?.TypeArguments.FirstOrDefault();
406428
if (fromType == null)
407-
return MatchOptions.GetOriginProperties(origin);
429+
return originProperties;
408430

409431
// cria a informação
410432
var info = AutoPropertiesGenerator.CreateInformation(origin, fromType, autoPropertiesAttribute);
411433

412434
// pega as propriedades da origem mais as da informação
413-
return [.. MatchOptions.GetOriginProperties(origin), .. info.Properties];
435+
return [.. originProperties, .. info.Properties];
414436
}
415437

416-
return MatchOptions.GetOriginProperties(origin);
438+
return originProperties;
417439
}
418440
}
441+
442+
internal class AutoDetailsAssignDescriptorResolver : IAssignDescriptorResolver
443+
{
444+
public bool TryCreateAssignDescriptor(
445+
TypeDescriptor leftType,
446+
TypeDescriptor rightType,
447+
SemanticModel model,
448+
MatchOptions options,
449+
out AssignDescriptor? descriptor)
450+
{
451+
descriptor = null;
452+
453+
// check if the left type has defined properties
454+
if (!leftType.HasDefinedProperties())
455+
return false;
456+
457+
// obtém propriedades do tipo de origem
458+
var leftProperties = options.OriginPropertiesRetriever.GetProperties(leftType);
459+
460+
// valida se tem propriedades
461+
if (leftProperties.Count == 0)
462+
return false;
463+
464+
// obtém propriedades do tipo de destino
465+
var rightProperties = options.TargetPropertiesRetriever.GetProperties(rightType);
466+
467+
// valida se tem propriedades
468+
if (rightProperties.Count == 0)
469+
return false;
470+
471+
// faz o match entre as propriedades
472+
// match das propriedades da classe com o attributo e a classe definida no TFrom.
473+
var matchSelection = MatchSelection.Create(leftType, leftProperties, rightType, rightProperties, model, options);
474+
475+
// se tem problemas, não é possível fazer o match.
476+
if (matchSelection.HasMissingProperties(out _) || matchSelection.HasNotAssignableProperties(out _))
477+
{
478+
return false;
479+
}
480+
481+
descriptor = new AssignDescriptor()
482+
{
483+
AssignType = AssignType.NewInstance,
484+
InnerSelection = matchSelection
485+
};
486+
487+
return true;
488+
}
489+
}

0 commit comments

Comments
 (0)