Skip to content

Commit 56265c5

Browse files
committed
Add property flattening support to AutoProperties
Updated ExtSrcGenVer to 0.1.8 in Directory.Build.props. Removed AttributeSyntaxExtensions.cs as it is no longer needed. Enhanced AutoPropertiesGenerator to support property flattening, including new logic and methods. Removed obsolete code. Added Flattening property to AutoPropertiesAttribute classes. Introduced AutoPropertiesFlatteningTests to verify flattening functionality.
1 parent f9b3920 commit 56265c5

6 files changed

Lines changed: 175 additions & 140 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.7</ExtSrcGenVer>
9+
<ExtSrcGenVer>0.1.8</ExtSrcGenVer>
1010
</PropertyGroup>
1111
<PropertyGroup>
1212
<Ver>0.2.0</Ver>

src/RoyalCode.SmartSelector.Generators/Extensions/AttributeSyntaxExtensions.cs

Lines changed: 0 additions & 89 deletions
This file was deleted.

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

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

56
namespace RoyalCode.SmartSelector.Generators.Generators;
67

@@ -14,7 +15,6 @@ internal static class AutoPropertiesGenerator
1415
internal static MatchOptions MatchOptions { get; } = new()
1516
{
1617
OriginPropertiesRetriever = new AutoPropertyOriginPropertiesRetriever(),
17-
//TargetPropertiesRetriever = new AutoPropertyTargetPropertiesRetriever(),
1818
};
1919

2020
internal static bool Predicate(SyntaxNode node, CancellationToken token)
@@ -111,19 +111,24 @@ classSymbol is null
111111
internal static AutoPropertiesInformation CreateInformation(
112112
TypeDescriptor modelType,
113113
TypeDescriptor fromType,
114-
AttributeSyntax properties)
114+
AttributeSyntax autoPropertyAttribute)
115115
{
116116
// collect excluded property names using extension helpers
117117
var excluded = new HashSet<string>(StringComparer.Ordinal);
118118

119-
// removido por hora
120-
////foreach (var name in properties.GetConstructorStringSet())
121-
//// excluded.Add(name);
122-
123-
foreach (var name in properties.GetNamedArgumentStringSet("Exclude"))
119+
foreach (var name in autoPropertyAttribute.GetNamedArgumentStrings("Exclude"))
124120
excluded.Add(name);
125121

126-
return CreateInformation(modelType, fromType, excluded);
122+
// collect flattening property names using extension helpers
123+
HashSet<string>? flattening = null;
124+
var flatteningNames = autoPropertyAttribute.GetNamedArgumentStrings("Flattening");
125+
foreach (var name in flatteningNames)
126+
{
127+
flattening ??= new HashSet<string>(StringComparer.Ordinal);
128+
flattening.Add(name);
129+
}
130+
131+
return CreateInformation(modelType, fromType, excluded, flattening);
127132
}
128133

129134
internal static AutoPropertiesInformation CreateInformation(
@@ -132,32 +137,30 @@ internal static AutoPropertiesInformation CreateInformation(
132137
AttributeData autoPropertyAttribute)
133138
{
134139
var excluded = new HashSet<string>(StringComparer.Ordinal);
135-
136-
// removido por hora
137-
////// obtém excluded do ctor ou propriedade Exclude
138-
////foreach (var name in autoPropertyAttribute.ConstructorArguments)
139-
//// if (name.Kind == TypedConstantKind.Array && name.Values != null)
140-
//// foreach (var v in name.Values)
141-
//// if (v.Value is string s)
142-
//// excluded.Add(s);
140+
var flattening = new HashSet<string>(StringComparer.Ordinal);
143141

144142
// obtém Exclude de NamedArguments
145143
foreach (var namedArg in autoPropertyAttribute.NamedArguments)
146144
if (namedArg.Key == "Exclude" && namedArg.Value.Kind == TypedConstantKind.Array && namedArg.Value.Values != null)
147145
foreach (var v in namedArg.Value.Values)
148146
if (v.Value is string s)
149147
excluded.Add(s);
148+
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);
150152

151153
// gera o TypeDescriptor do fromType
152154
var fromTypeDescriptor = fromType.CreateTypeDescriptor();
153155

154-
return CreateInformation(modelType, fromTypeDescriptor, excluded);
156+
return CreateInformation(modelType, fromTypeDescriptor, excluded, flattening);
155157
}
156158

157159
internal static AutoPropertiesInformation CreateInformation(
158160
TypeDescriptor modelType,
159161
TypeDescriptor fromType,
160-
HashSet<string> excluded)
162+
HashSet<string> excluded,
163+
HashSet<string>? flattening)
161164
{
162165
// declared properties in model type are always excluded
163166
foreach (var p in modelType.CreateProperties(p => p.SetMethod is not null))
@@ -167,23 +170,67 @@ internal static AutoPropertiesInformation CreateInformation(
167170

168171
// filtra propriedades do source,
169172
// remove propriedades que estão na lista de excluídas,
173+
// remove propriedades de flattening (serão recriadas depois)
170174
// removendo o que não for tipo primitivo, string, decimal, DateTime,
171175
// enum ou nullable desses tipos, além de coleções de tipos primitivos,
172176
// aceita structs também.
173-
sourceProps = sourceProps
177+
var autoProps = sourceProps
174178
.Where(p => !excluded.Contains(p.Name))
179+
.Where(p => flattening is null || !flattening.Contains(p.Name))
175180
.Where(IsSupportedType)
176-
.ToArray();
181+
.ToList();
182+
183+
// processa flattening se houver
184+
if (flattening is not null)
185+
foreach (var fp in CreateFlattening(fromType, sourceProps, flattening))
186+
if (!autoProps.Any(p => p.Name == fp.Name))
187+
autoProps.Add(fp);
177188

178189
var generated = new List<PropertyDescriptor>();
179-
foreach (var p in sourceProps)
190+
foreach (var p in autoProps)
180191
{
181192
generated.Add(new PropertyDescriptor(p.Type, p.Name, p.Symbol));
182193
}
183194

184195
return new AutoPropertiesInformation(modelType, [.. generated]);
185196
}
186197

198+
private static IReadOnlyList<PropertyDescriptor> CreateFlattening(
199+
TypeDescriptor fromType,
200+
IReadOnlyList<PropertyDescriptor> sourceProps,
201+
HashSet<string> flattening)
202+
{
203+
var list = new List<PropertyDescriptor>();
204+
var matchTypeInfo = new MatchTypeInfo(fromType, sourceProps, MatchOptions.Default);
205+
206+
foreach (var flattenPropName in flattening)
207+
{
208+
var flattenProp = new PropertyDescriptor(TypeDescriptor.Void(), flattenPropName, null);
209+
var selection = PropertySelection.Select(flattenProp, matchTypeInfo);
210+
211+
if (selection == null)
212+
continue;
213+
214+
if (!selection.PropertyType.Type.HasNamedTypeSymbol(out var namedType))
215+
continue;
216+
217+
// se não for classe ou struct, não faz flattening
218+
if (namedType.TypeKind != TypeKind.Class && namedType.TypeKind != TypeKind.Struct)
219+
continue;
220+
221+
// obtém as propriedades do tipo
222+
var nestedProps = namedType.CreateTypeDescriptor().CreateProperties(p => p.GetMethod is not null);
223+
foreach (var np in nestedProps.Where(IsSupportedType))
224+
{
225+
// cria nova propriedade com o nome composto
226+
var newPropName = $"{flattenPropName}{np.Name}";
227+
list.Add(new PropertyDescriptor(np.Type, newPropName, np.Symbol));
228+
}
229+
}
230+
231+
return list;
232+
}
233+
187234
private static readonly HashSet<string> SupportedPrimitiveTypes = new(StringComparer.Ordinal)
188235
{
189236
"bool",
@@ -369,11 +416,3 @@ public IReadOnlyList<PropertyDescriptor> GetProperties(TypeDescriptor origin)
369416
return MatchOptions.GetOriginProperties(origin);
370417
}
371418
}
372-
373-
//internal class AutoPropertyTargetPropertiesRetriever : ITargetPropertiesRetriever
374-
//{
375-
// public IReadOnlyList<PropertyDescriptor> GetProperties(TypeDescriptor target)
376-
// {
377-
// throw new NotImplementedException();
378-
// }
379-
//}

src/RoyalCode.SmartSelector.Generators/Generators/AutoPropertiesInformation.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using Microsoft.CodeAnalysis;
2-
using RoyalCode.Extensions.SourceGenerator.Descriptors.PropertySelection;
32

43
namespace RoyalCode.SmartSelector.Generators.Generators;
54

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
using FluentAssertions;
2+
using Microsoft.CodeAnalysis;
3+
4+
namespace RoyalCode.SmartSelector.Tests.Tests;
5+
6+
public class AutoPropertiesFlatteningTests
7+
{
8+
[Fact]
9+
public void Should_Generate_Flattened_Properties_With_NonGeneric_AutoProperties()
10+
{
11+
// arrange + act
12+
Util.Compile(Source.NonGeneric, out var compilation, out var diagnostics);
13+
14+
// assert - nenhum erro de geração
15+
diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).Should().BeEmpty();
16+
17+
var generated = string.Join("\n-----\n", compilation.SyntaxTrees.Skip(1).Select(t => t.ToString()));
18+
19+
// propriedades simples continuam
20+
generated.Should().Contain("public int Id { get; set; }");
21+
// propriedade complexa original NÃO deve ser criada automaticamente (foi flattening)
22+
generated.Should().NotContain("public Nested Nested { get; set; }");
23+
// propriedades flatten criadas
24+
generated.Should().Contain("public string NestedValue { get; set; }");
25+
generated.Should().Contain("public int NestedCount { get; set; }");
26+
}
27+
28+
[Fact]
29+
public void Should_Generate_Flattened_Properties_With_Generic_AutoProperties()
30+
{
31+
// arrange + act
32+
Util.Compile(Source.Generic, out var compilation, out var diagnostics);
33+
34+
// assert - nenhum erro de geração
35+
diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).Should().BeEmpty();
36+
37+
var generated = string.Join("\n-----\n", compilation.SyntaxTrees.Skip(1).Select(t => t.ToString()));
38+
39+
generated.Should().Contain("public int Id { get; set; }");
40+
generated.Should().NotContain("public Nested Nested { get; set; }");
41+
generated.Should().Contain("public string NestedValue { get; set; }");
42+
generated.Should().Contain("public int NestedCount { get; set; }");
43+
}
44+
}
45+
46+
file static class Source
47+
{
48+
public const string NonGeneric =
49+
"""
50+
using System;
51+
using RoyalCode.SmartSelector;
52+
53+
namespace Tests.SmartSelector.Models;
54+
55+
[AutoSelect<Origin>]
56+
[AutoProperties(Flattening = new [] { nameof(Origin.Nested) })]
57+
public partial class Dto { }
58+
59+
public class Origin
60+
{
61+
public int Id { get; set; }
62+
public Nested Nested { get; set; } = new();
63+
}
64+
65+
public class Nested
66+
{
67+
public string Value { get; set; } = string.Empty;
68+
public int Count { get; set; }
69+
}
70+
""";
71+
72+
public const string Generic =
73+
"""
74+
using System;
75+
using RoyalCode.SmartSelector;
76+
77+
namespace Tests.SmartSelector.Models;
78+
79+
[AutoProperties<Origin>(Flattening = new [] { nameof(Origin.Nested) })]
80+
public partial class Dto { }
81+
82+
public class Origin
83+
{
84+
public int Id { get; set; }
85+
public Nested Nested { get; set; } = new();
86+
}
87+
88+
public class Nested
89+
{
90+
public string Value { get; set; } = string.Empty;
91+
public int Count { get; set; }
92+
}
93+
""";
94+
}

0 commit comments

Comments
 (0)