-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
197 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
using System.Reflection; | ||
using Antelcat.AutoGen.ComponentModel.Diagnostic; | ||
|
||
namespace Antelcat.AutoGen.Sample.Models.Diagnostics; | ||
|
||
|
||
[AutoMetadataFrom(typeof(Simulator), MemberTypes.Property, | ||
Leading = "public global::System.Collections.Generic.IEnumerable<string> Writables(){", | ||
Template = | ||
""" | ||
#if {CanWrite} | ||
yield return nameof({Name}); | ||
#endif | ||
""", | ||
Final = "}")] | ||
public partial class Simulator | ||
{ | ||
public string A { get; set; } | ||
public string B { get; } | ||
public string C { get; set; } | ||
public string D { get; } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
37 changes: 37 additions & 0 deletions
37
src/Antelcat.AutoGen.Shared/ComponentModel/Diagnostic/AutoMetadataFrom.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
using System; | ||
using System.Reflection; | ||
using Antelcat.AutoGen.ComponentModel.Abstractions; | ||
|
||
namespace Antelcat.AutoGen.ComponentModel.Diagnostic | ||
{ | ||
/// <summary> | ||
/// Auto generate code using <see cref="Template"/> and members | ||
/// specified by <see cref="MemberTypes"/> from given <see cref="Type"/> | ||
/// </summary> | ||
/// <param name="forType">Type which contains target members</param> | ||
/// <param name="memberTypes">Types of the members</param> | ||
[AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = true, | ||
Inherited = false)] | ||
public class AutoMetadataFrom(Type forType, MemberTypes memberTypes) : AutoGenAttribute | ||
{ | ||
internal Type ForType => forType; | ||
internal MemberTypes MemberTypes => memberTypes; | ||
|
||
/// <summary> | ||
/// Template applying to generate code, you can use | ||
/// {Name} {PropertyType} {CanRead} ... those members | ||
/// come from inherits of <see cref="MemberInfo"/> | ||
/// </summary> | ||
public string Template { get; set; } = string.Empty; | ||
|
||
/// <summary> | ||
/// Plain text added to the leading of generated code | ||
/// </summary> | ||
public string? Leading { get; set; } | ||
|
||
/// <summary> | ||
/// Plain text added to the final of generated code | ||
/// </summary> | ||
public string? Final { get; set; } | ||
} | ||
} |
123 changes: 123 additions & 0 deletions
123
src/Antelcat.AutoGen.SourceGenerators/Generators/Diagnostic/MetadataGenerator.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
using System; | ||
using System.Collections.Concurrent; | ||
using System.Collections.Generic; | ||
using System.Collections.Immutable; | ||
using System.Linq; | ||
using System.Reflection; | ||
using System.Text; | ||
using System.Text.RegularExpressions; | ||
using Antelcat.AutoGen.ComponentModel.Diagnostic; | ||
using Antelcat.AutoGen.SourceGenerators.Extensions; | ||
using Antelcat.AutoGen.SourceGenerators.Generators.Base; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
|
||
namespace Antelcat.AutoGen.SourceGenerators.Generators.Diagnostic; | ||
|
||
[Generator(LanguageNames.CSharp)] | ||
public class MetadataGenerator : AttributeDetectBaseGenerator<AutoMetadataFrom> | ||
{ | ||
private static IEnumerable<string> GetPlaceholders(string template) | ||
{ | ||
foreach (var match in Regex.Matches(template, "{[\\w+((\\.)?)]+}")) | ||
{ | ||
var str = match.ToString(); | ||
yield return str.Substring(1, str.Length - 2); | ||
} | ||
} | ||
|
||
private static StringBuilder Resolve(MemberInfo info, StringBuilder stringBuilder) | ||
{ | ||
foreach (var placeholder in GetPlaceholders(stringBuilder.ToString())) | ||
{ | ||
object value = info; | ||
foreach (var part in placeholder.Split('.')) | ||
{ | ||
if (value is not MemberInfo memberInfo) continue; | ||
var val = GetValue(memberInfo, part); | ||
if (val is null) return stringBuilder; | ||
value = val; | ||
} | ||
|
||
stringBuilder = stringBuilder.Replace('{' + placeholder + '}', | ||
value is Feast.CodeAnalysis.CompileTime.Type compile | ||
? compile.Symbol.GetFullyQualifiedName() | ||
: value.ToString()); | ||
} | ||
|
||
return stringBuilder; | ||
} | ||
|
||
private static object? GetValue(MemberInfo info, string propertyName) | ||
{ | ||
var type = info.GetType(); | ||
return PropsMap.GetOrAdd(info.GetType(), | ||
_ => | ||
{ | ||
ConcurrentDictionary<string, PropertyInfo?> ret = []; | ||
var prop = type.GetProperty(propertyName); | ||
if (prop is null) return ret; | ||
ret.TryAdd(propertyName, prop); | ||
return ret; | ||
}).GetOrAdd(propertyName, _ => type.GetProperty(propertyName))? | ||
.GetValue(info); | ||
} | ||
|
||
private static readonly ConcurrentDictionary<Type, ConcurrentDictionary<string, PropertyInfo?>> PropsMap = []; | ||
|
||
protected override bool FilterSyntax(SyntaxNode node) => true; | ||
|
||
protected override void Initialize(SourceProductionContext context, | ||
Compilation compilation, | ||
ImmutableArray<GeneratorAttributeSyntaxContext> syntaxArray) | ||
{ | ||
foreach (var groupedSyntaxContext in syntaxArray.GroupBy(x => (x.TargetSymbol as INamedTypeSymbol)!, | ||
SymbolEqualityComparer.Default)) | ||
{ | ||
var @class = (groupedSyntaxContext.Key as INamedTypeSymbol)!; | ||
foreach (var syntaxContext in groupedSyntaxContext) | ||
{ | ||
foreach (var (metadata, index) in syntaxContext.Attributes.GetAttributes<AutoMetadataFrom>() | ||
.Select((x, i) => (x, i))) | ||
{ | ||
var partial = @class.PartialTypeDeclaration(); | ||
List<string> members = []; | ||
if (metadata.Leading != null) members.Add(metadata.Leading); | ||
var target = metadata.ForType; | ||
var fileName = | ||
$"{@class.ToType().QualifiedFullFileName()}_From_{target.QualifiedFullFileName()}_{index}.cs"; | ||
|
||
const BindingFlags flags = BindingFlags.NonPublic | | ||
BindingFlags.Public | | ||
BindingFlags.Instance | | ||
BindingFlags.Static; | ||
Map(MemberTypes.Field, () => target.GetFields(flags).Where(x => !x.IsSpecialName)); | ||
Map(MemberTypes.Property, () => target.GetProperties(flags)); | ||
Map(MemberTypes.Constructor, () => target.GetConstructors(flags)); | ||
Map(MemberTypes.NestedType, () => target.GetNestedTypes(flags)); | ||
Map(MemberTypes.Event, () => target.GetEvents(flags)); | ||
Map(MemberTypes.Method, () => target.GetMethods(flags).Where(x => !x.IsSpecialName)); | ||
|
||
if (metadata.Final != null) members.Add(metadata.Final); | ||
var member = ParseMemberDeclaration(string.Join("", members)); | ||
if (member != null) partial = partial.AddMembers(member); | ||
|
||
var file = CompilationUnit() | ||
.AddPartialType(@class, x => partial) | ||
.NormalizeWhitespace(); | ||
context.AddSource(fileName, file.GetText(Encoding.UTF8)); | ||
continue; | ||
|
||
void Map(MemberTypes memberTypes, Func<IEnumerable<MemberInfo>> memberGetter) | ||
{ | ||
if (!metadata.MemberTypes.HasFlag(memberTypes)) return; | ||
members.AddRange(memberGetter() | ||
.Select(field => Resolve(field, new StringBuilder(metadata.Template)) | ||
.ToString()) | ||
); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters