Skip to content

Commit 9045bfd

Browse files
committed
Update AutoMetadata
1 parent 9f6a7be commit 9045bfd

10 files changed

Lines changed: 197 additions & 8 deletions

File tree

README.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,18 @@ Auto generate anything you may want
99

1010
### `Antelcat.AutoGen.ComponentModel` :
1111

12+
+ #### `[AutoMetadataFrom(Type, MemberTypes)]`
13+
Auto generate code using `Template` from target type members
14+
15+
![AutoMetadata](./docs/AutoMetadata.png)
16+
1217
+ #### `[AutoStringTo(string, Accessibility)]` :
1318

1419
Auto generate string To extension
1520

1621
only on `assembly` and `static partial class`
1722

18-
![AutoStringTo](./docs/GenerateStringTo.png)
23+
![AutoStringTo](./docs/AutoStringTo.png)
1924

2025
+ #### `Mapping` :
2126

@@ -25,7 +30,7 @@ Auto generate anything you may want
2530

2631
> Only on `partial method`
2732
28-
![AutoMapTo](./docs/GenerateMap.png)
33+
![AutoMapTo](./docs/AutoMap.png)
2934

3035
> You can use to generate `shallow copy`
3136
@@ -49,7 +54,7 @@ Auto generate anything you may want
4954

5055
+ #### `[MapConstructor(params string[])]` :
5156

52-
Specified property to be added in constructor, will auto detect if `null`
57+
Specified property to be added in constructor, will auto-detect if `null`
5358

5459

5560
+ #### `[AutoFilePath]`:
File renamed without changes.

docs/AutoMetadata.png

92.7 KB
Loading
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using System.Reflection;
2+
using Antelcat.AutoGen.ComponentModel.Diagnostic;
3+
4+
namespace Antelcat.AutoGen.Sample.Models.Diagnostics;
5+
6+
7+
[AutoMetadataFrom(typeof(Simulator), MemberTypes.Property,
8+
Leading = "public global::System.Collections.Generic.IEnumerable<string> Writables(){",
9+
Template =
10+
"""
11+
#if {CanWrite}
12+
yield return nameof({Name});
13+
#endif
14+
15+
""",
16+
Final = "}")]
17+
public partial class Simulator
18+
{
19+
public string A { get; set; }
20+
public string B { get; }
21+
public string C { get; set; }
22+
public string D { get; }
23+
}

src/Antelcat.AutoGen.Sample/Models/Mapping/FileDescriptor.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ namespace Antelcat.AutoGen.Sample.Models.Mapping;
66

77
public partial class FileDescriptor : ObservableObject
88
{
9-
public required string FullName { get; set; }
10-
public virtual long Length { get; set; }
9+
public required string FullName { get; set; }
10+
public virtual long Length { get; set; }
1111

1212
[ObservableProperty] private string property;
1313
}

src/Antelcat.AutoGen.Shared/Antelcat.AutoGen.Shared.projitems

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
<Compile Include="$(MSBuildThisFileDirectory)ComponentModel\AutoKeyAccessorAttribute.cs" />
1818
<Compile Include="$(MSBuildThisFileDirectory)ComponentModel\AutoKeyEnumerableAttribute.cs" />
1919
<Compile Include="$(MSBuildThisFileDirectory)ComponentModel\AutoStringToAttribute.cs" />
20+
<Compile Include="$(MSBuildThisFileDirectory)ComponentModel\Diagnostic\AutoMetadataFrom.cs" />
2021
<Compile Include="$(MSBuildThisFileDirectory)ComponentModel\Diagnostic\AutoReport.cs" />
2122
<Compile Include="$(MSBuildThisFileDirectory)ComponentModel\Diagnostic\AutoWatchAttribute.cs" />
2223
<Compile Include="$(MSBuildThisFileDirectory)ComponentModel\Mapping\AutoMapAttribute.cs" />
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using System;
2+
using System.Reflection;
3+
using Antelcat.AutoGen.ComponentModel.Abstractions;
4+
5+
namespace Antelcat.AutoGen.ComponentModel.Diagnostic
6+
{
7+
/// <summary>
8+
/// Auto generate code using <see cref="Template"/> and members
9+
/// specified by <see cref="MemberTypes"/> from given <see cref="Type"/>
10+
/// </summary>
11+
/// <param name="forType">Type which contains target members</param>
12+
/// <param name="memberTypes">Types of the members</param>
13+
[AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = true,
14+
Inherited = false)]
15+
public class AutoMetadataFrom(Type forType, MemberTypes memberTypes) : AutoGenAttribute
16+
{
17+
internal Type ForType => forType;
18+
internal MemberTypes MemberTypes => memberTypes;
19+
20+
/// <summary>
21+
/// Template applying to generate code, you can use
22+
/// {Name} {PropertyType} {CanRead} ... those members
23+
/// come from inherits of <see cref="MemberInfo"/>
24+
/// </summary>
25+
public string Template { get; set; } = string.Empty;
26+
27+
/// <summary>
28+
/// Plain text added to the leading of generated code
29+
/// </summary>
30+
public string? Leading { get; set; }
31+
32+
/// <summary>
33+
/// Plain text added to the final of generated code
34+
/// </summary>
35+
public string? Final { get; set; }
36+
}
37+
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
using System;
2+
using System.Collections.Concurrent;
3+
using System.Collections.Generic;
4+
using System.Collections.Immutable;
5+
using System.Linq;
6+
using System.Reflection;
7+
using System.Text;
8+
using System.Text.RegularExpressions;
9+
using Antelcat.AutoGen.ComponentModel.Diagnostic;
10+
using Antelcat.AutoGen.SourceGenerators.Extensions;
11+
using Antelcat.AutoGen.SourceGenerators.Generators.Base;
12+
using Microsoft.CodeAnalysis;
13+
using Microsoft.CodeAnalysis.CSharp.Syntax;
14+
15+
namespace Antelcat.AutoGen.SourceGenerators.Generators.Diagnostic;
16+
17+
[Generator(LanguageNames.CSharp)]
18+
public class MetadataGenerator : AttributeDetectBaseGenerator<AutoMetadataFrom>
19+
{
20+
private static IEnumerable<string> GetPlaceholders(string template)
21+
{
22+
foreach (var match in Regex.Matches(template, "{[\\w+((\\.)?)]+}"))
23+
{
24+
var str = match.ToString();
25+
yield return str.Substring(1, str.Length - 2);
26+
}
27+
}
28+
29+
private static StringBuilder Resolve(MemberInfo info, StringBuilder stringBuilder)
30+
{
31+
foreach (var placeholder in GetPlaceholders(stringBuilder.ToString()))
32+
{
33+
object value = info;
34+
foreach (var part in placeholder.Split('.'))
35+
{
36+
if (value is not MemberInfo memberInfo) continue;
37+
var val = GetValue(memberInfo, part);
38+
if (val is null) return stringBuilder;
39+
value = val;
40+
}
41+
42+
stringBuilder = stringBuilder.Replace('{' + placeholder + '}',
43+
value is Feast.CodeAnalysis.CompileTime.Type compile
44+
? compile.Symbol.GetFullyQualifiedName()
45+
: value.ToString());
46+
}
47+
48+
return stringBuilder;
49+
}
50+
51+
private static object? GetValue(MemberInfo info, string propertyName)
52+
{
53+
var type = info.GetType();
54+
return PropsMap.GetOrAdd(info.GetType(),
55+
_ =>
56+
{
57+
ConcurrentDictionary<string, PropertyInfo?> ret = [];
58+
var prop = type.GetProperty(propertyName);
59+
if (prop is null) return ret;
60+
ret.TryAdd(propertyName, prop);
61+
return ret;
62+
}).GetOrAdd(propertyName, _ => type.GetProperty(propertyName))?
63+
.GetValue(info);
64+
}
65+
66+
private static readonly ConcurrentDictionary<Type, ConcurrentDictionary<string, PropertyInfo?>> PropsMap = [];
67+
68+
protected override bool FilterSyntax(SyntaxNode node) => true;
69+
70+
protected override void Initialize(SourceProductionContext context,
71+
Compilation compilation,
72+
ImmutableArray<GeneratorAttributeSyntaxContext> syntaxArray)
73+
{
74+
foreach (var groupedSyntaxContext in syntaxArray.GroupBy(x => (x.TargetSymbol as INamedTypeSymbol)!,
75+
SymbolEqualityComparer.Default))
76+
{
77+
var @class = (groupedSyntaxContext.Key as INamedTypeSymbol)!;
78+
foreach (var syntaxContext in groupedSyntaxContext)
79+
{
80+
foreach (var (metadata, index) in syntaxContext.Attributes.GetAttributes<AutoMetadataFrom>()
81+
.Select((x, i) => (x, i)))
82+
{
83+
var partial = @class.PartialTypeDeclaration();
84+
List<string> members = [];
85+
if (metadata.Leading != null) members.Add(metadata.Leading);
86+
var target = metadata.ForType;
87+
var fileName =
88+
$"{@class.ToType().QualifiedFullFileName()}_From_{target.QualifiedFullFileName()}_{index}.cs";
89+
90+
const BindingFlags flags = BindingFlags.NonPublic |
91+
BindingFlags.Public |
92+
BindingFlags.Instance |
93+
BindingFlags.Static;
94+
Map(MemberTypes.Field, () => target.GetFields(flags).Where(x => !x.IsSpecialName));
95+
Map(MemberTypes.Property, () => target.GetProperties(flags));
96+
Map(MemberTypes.Constructor, () => target.GetConstructors(flags));
97+
Map(MemberTypes.NestedType, () => target.GetNestedTypes(flags));
98+
Map(MemberTypes.Event, () => target.GetEvents(flags));
99+
Map(MemberTypes.Method, () => target.GetMethods(flags).Where(x => !x.IsSpecialName));
100+
101+
if (metadata.Final != null) members.Add(metadata.Final);
102+
var member = ParseMemberDeclaration(string.Join("", members));
103+
if (member != null) partial = partial.AddMembers(member);
104+
105+
var file = CompilationUnit()
106+
.AddPartialType(@class, x => partial)
107+
.NormalizeWhitespace();
108+
context.AddSource(fileName, file.GetText(Encoding.UTF8));
109+
continue;
110+
111+
void Map(MemberTypes memberTypes, Func<IEnumerable<MemberInfo>> memberGetter)
112+
{
113+
if (!metadata.MemberTypes.HasFlag(memberTypes)) return;
114+
members.AddRange(memberGetter()
115+
.Select(field => Resolve(field, new StringBuilder(metadata.Template))
116+
.ToString())
117+
);
118+
}
119+
}
120+
}
121+
}
122+
}
123+
}

src/Antelcat.AutoGen/Antelcat.AutoGen.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
<IsPackable>true</IsPackable>
88
<LangVersion>preview</LangVersion>
99

10-
<Version>1.2.7</Version>
11-
<FileVersion>1.2.7</FileVersion>
12-
<AssemblyVersion>1.2.7</AssemblyVersion>
10+
<Version>2.0.0-preview1</Version>
11+
<FileVersion>2.0.0</FileVersion>
12+
<AssemblyVersion>2.0.0</AssemblyVersion>
1313

1414
<Authors>Antelcat</Authors>
1515
<Title>Antelcat.AutoGen</Title>

0 commit comments

Comments
 (0)