mirror of
https://github.com/esiur/esiur-dotnet.git
synced 2025-09-13 12:43:17 +00:00
1
This commit is contained in:
@@ -43,7 +43,7 @@
|
|||||||
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
||||||
<PackageReference Include="System.Reflection.Emit" Version="4.7.0" />
|
<PackageReference Include="System.Reflection.Emit" Version="4.7.0" />
|
||||||
<PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.7.0" />
|
<PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.7.0" />
|
||||||
<PackageReference Include="System.Text.Json" Version="8.0.5" GeneratePathProperty="true" />
|
<PackageReference Include="System.Text.Json" Version="8.0.6" GeneratePathProperty="true" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
||||||
|
@@ -1,188 +1,81 @@
|
|||||||
using Microsoft.CodeAnalysis;
|
// ================================
|
||||||
using Microsoft.CodeAnalysis.CSharp;
|
// FILE: ResourceIncrementalGenerator.cs
|
||||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
// Replaces: ResourceGenerator.cs + ResourceGeneratorReceiver.cs
|
||||||
using Microsoft.CodeAnalysis.Text;
|
// ================================
|
||||||
using System;
|
using Esiur.Core;
|
||||||
using System.Collections.Generic;
|
using Esiur.Data;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Text;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using Esiur.Net.IIP;
|
using Esiur.Net.IIP;
|
||||||
using Esiur.Resource;
|
using Esiur.Resource;
|
||||||
using Esiur.Resource.Template;
|
using Esiur.Resource.Template;
|
||||||
using Esiur.Data;
|
using Microsoft.CodeAnalysis;
|
||||||
using System.IO;
|
using Microsoft.CodeAnalysis.CSharp;
|
||||||
using Esiur.Core;
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace Esiur.Proxy;
|
namespace Esiur.Proxy
|
||||||
|
|
||||||
[Generator]
|
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("MicrosoftCodeAnalysisCorrectness", "RS1036:Specify analyzer banned API enforcement setting", Justification = "<Pending>")]
|
|
||||||
public class ResourceGenerator : IIncrementalGenerator
|
|
||||||
{
|
{
|
||||||
|
[Generator(LanguageNames.CSharp)]
|
||||||
private KeyList<string, TypeTemplate[]> cache = new();
|
public sealed class ResourceGenerator : IIncrementalGenerator
|
||||||
// private List<string> inProgress = new();
|
|
||||||
|
|
||||||
public void Initialize(GeneratorInitializationContext context)
|
|
||||||
{
|
{
|
||||||
// Register receiver
|
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||||
|
|
||||||
context.RegisterForSyntaxNotifications(() => new ResourceGeneratorReceiver());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void ReportError(GeneratorExecutionContext context, string title, string msg, string category)
|
|
||||||
{
|
|
||||||
context.ReportDiagnostic(Diagnostic.Create(new DiagnosticDescriptor("MySG001", title, msg, category, DiagnosticSeverity.Error, true), Location.None));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void GenerateModel(GeneratorExecutionContext context, TypeTemplate[] templates)
|
|
||||||
{
|
|
||||||
foreach (var tmp in templates)
|
|
||||||
{
|
{
|
||||||
if (tmp.Type == TemplateType.Resource)
|
// 1) Discover candidate classes via a cheap syntax filter
|
||||||
|
var perClass = context.SyntaxProvider.CreateSyntaxProvider(
|
||||||
|
(node, _) => node is ClassDeclarationSyntax cds && cds.AttributeLists.Count > 0,
|
||||||
|
(ctx, _) => AnalyzeClass(ctx)
|
||||||
|
)
|
||||||
|
.Where( x => x is not null)!;
|
||||||
|
|
||||||
|
// 2) Aggregate import URLs (distinct)
|
||||||
|
var importUrls = perClass
|
||||||
|
.SelectMany((x, y) => x.Value.ImportUrls)
|
||||||
|
.Collect()
|
||||||
|
.Select( (urls, y) => urls.Distinct(StringComparer.Ordinal).ToImmutableArray());
|
||||||
|
|
||||||
|
// 3) Aggregate class infos and merge partials by stable key
|
||||||
|
var mergedResources = perClass
|
||||||
|
.Select( (x, y) => x.Value.ClassInfo)
|
||||||
|
.Where( ci => ci is not null)!
|
||||||
|
.Select( (ci, y) => ci!)
|
||||||
|
.Collect()
|
||||||
|
.Select( (list, y) => MergePartials(list));
|
||||||
|
|
||||||
|
// 4) Generate: A) remote templates (from ImportAttribute URLs)
|
||||||
|
context.RegisterSourceOutput(importUrls, (spc, urls) =>
|
||||||
{
|
{
|
||||||
var source = TemplateGenerator.GenerateClass(tmp, templates, false);
|
if (urls.Length == 0) return;
|
||||||
context.AddSource(tmp.ClassName + ".Generated.cs", source);
|
foreach (var path in urls)
|
||||||
}
|
|
||||||
else if (tmp.Type == TemplateType.Record)
|
|
||||||
{
|
|
||||||
var source = TemplateGenerator.GenerateRecord(tmp, templates);
|
|
||||||
context.AddSource(tmp.ClassName + ".Generated.cs", source);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// generate info class
|
|
||||||
|
|
||||||
|
|
||||||
var typesFile = "using System; \r\n namespace Esiur { public static class Generated { public static Type[] Resources {get;} = new Type[] { " +
|
|
||||||
string.Join(",", templates.Where(x => x.Type == TemplateType.Resource).Select(x => $"typeof({x.ClassName})"))
|
|
||||||
+ " }; \r\n public static Type[] Records { get; } = new Type[] { " +
|
|
||||||
string.Join(",", templates.Where(x => x.Type == TemplateType.Record).Select(x => $"typeof({x.ClassName})"))
|
|
||||||
+ " }; " +
|
|
||||||
|
|
||||||
"\r\n } \r\n}";
|
|
||||||
|
|
||||||
context.AddSource("Esiur.Generated.cs", typesFile);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static string SuggestExportName(string fieldName)
|
|
||||||
{
|
|
||||||
if (Char.IsUpper(fieldName[0]))
|
|
||||||
return fieldName.Substring(0, 1).ToLower() + fieldName.Substring(1);
|
|
||||||
else
|
|
||||||
return fieldName.Substring(0, 1).ToUpper() + fieldName.Substring(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string FormatAttribute(AttributeData attribute)
|
|
||||||
{
|
|
||||||
if (!(attribute.AttributeClass is object))
|
|
||||||
throw new Exception("AttributeClass not found");
|
|
||||||
|
|
||||||
var className = attribute.AttributeClass.ToDisplayString();
|
|
||||||
|
|
||||||
if (!attribute.ConstructorArguments.Any() & !attribute.ConstructorArguments.Any())
|
|
||||||
return $"[{className}]";
|
|
||||||
|
|
||||||
var strBuilder = new StringBuilder();
|
|
||||||
|
|
||||||
strBuilder.Append("[");
|
|
||||||
strBuilder.Append(className);
|
|
||||||
strBuilder.Append("(");
|
|
||||||
|
|
||||||
strBuilder.Append(String.Join(", ", attribute.ConstructorArguments.Select(ca => FormatConstant(ca))));
|
|
||||||
|
|
||||||
strBuilder.Append(String.Join(", ", attribute.NamedArguments.Select(na => $"{na.Key} = {FormatConstant(na.Value)}")));
|
|
||||||
|
|
||||||
strBuilder.Append(")]");
|
|
||||||
|
|
||||||
return strBuilder.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string FormatConstant(TypedConstant constant)
|
|
||||||
{
|
|
||||||
if (constant.Kind == TypedConstantKind.Array)
|
|
||||||
return $"new {constant.Type.ToDisplayString()} {{{string.Join(", ", constant.Values.Select(v => FormatConstant(v)))}}}";
|
|
||||||
else
|
|
||||||
return constant.ToCSharpString();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void Execute(GeneratorExecutionContext context)
|
|
||||||
{
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
|
|
||||||
|
|
||||||
if (!(context.SyntaxContextReceiver is ResourceGeneratorReceiver receiver))
|
|
||||||
return;
|
|
||||||
|
|
||||||
//if (receiver.Imports.Count > 0 && !Debugger.IsAttached)
|
|
||||||
//{
|
|
||||||
// Debugger.Launch();
|
|
||||||
//}
|
|
||||||
|
|
||||||
foreach (var path in receiver.Imports)
|
|
||||||
{
|
|
||||||
if (!TemplateGenerator.urlRegex.IsMatch(path))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (cache.Contains(path))
|
|
||||||
{
|
{
|
||||||
GenerateModel(context, cache[path]);
|
try
|
||||||
continue;
|
{
|
||||||
|
if (!TemplateGenerator.urlRegex.IsMatch(path))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var parts = TemplateGenerator.urlRegex.Split(path);
|
||||||
|
var con = Warehouse.Default.Get<DistributedConnection>($"{parts[1]}://{parts[2]}").Wait(20000);
|
||||||
|
var templates = con.GetLinkTemplates(parts[3]).Wait(60000);
|
||||||
|
|
||||||
|
EmitTemplates(spc, templates);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Report(spc, "Esiur", ex.Message, DiagnosticSeverity.Error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Syncronization
|
// 4) Generate: B) per resource partials (properties/events, base IResource impl if needed)
|
||||||
//if (inProgress.Contains(path))
|
context.RegisterSourceOutput(mergedResources, static (spc, classes) =>
|
||||||
// continue;
|
|
||||||
|
|
||||||
//inProgress.Add(path);
|
|
||||||
|
|
||||||
var url = TemplateGenerator.urlRegex.Split(path);
|
|
||||||
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var con = Warehouse.Default.Get<DistributedConnection>(url[1] + "://" + url[2]).Wait(20000);
|
|
||||||
var templates = con.GetLinkTemplates(url[3]).Wait(60000);
|
|
||||||
|
|
||||||
cache[path] = templates;
|
|
||||||
|
|
||||||
// make sources
|
|
||||||
GenerateModel(context, templates);
|
|
||||||
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
ReportError(context, ex.Source, ex.Message, "Esiur");
|
|
||||||
}
|
|
||||||
|
|
||||||
//inProgress.Remove(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//#if DEBUG
|
|
||||||
|
|
||||||
//#endif
|
|
||||||
|
|
||||||
//var toImplement = receiver.Classes.Where(x => x.Fields.Length > 0);
|
|
||||||
|
|
||||||
foreach (var ci in receiver.Classes.Values)
|
|
||||||
{
|
{
|
||||||
try
|
foreach (var ci in classes)
|
||||||
{
|
{
|
||||||
|
try
|
||||||
var code = @$"using Esiur.Resource;
|
{
|
||||||
|
var code = @$"using Esiur.Resource;
|
||||||
using Esiur.Core;
|
using Esiur.Core;
|
||||||
|
|
||||||
#nullable enable
|
#nullable enable
|
||||||
@@ -190,68 +83,230 @@ using Esiur.Core;
|
|||||||
namespace {ci.ClassSymbol.ContainingNamespace.ToDisplayString()} {{
|
namespace {ci.ClassSymbol.ContainingNamespace.ToDisplayString()} {{
|
||||||
";
|
";
|
||||||
|
|
||||||
if (ci.IsInterfaceImplemented(receiver.Classes))
|
if (IsInterfaceImplemented(ci, classes))
|
||||||
code += $"public partial class {ci.Name} {{\r\n";
|
code += $"public partial class {ci.Name} {{\r\n";
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
code +=
|
code +=
|
||||||
@$" public partial class {ci.Name} : IResource {{
|
$@" public partial class {ci.Name} : IResource {{
|
||||||
public virtual Instance? Instance {{ get; set; }}
|
public virtual Instance? Instance {{ get; set; }}
|
||||||
public virtual event DestroyedEvent? OnDestroy;
|
public virtual event DestroyedEvent? OnDestroy;
|
||||||
|
|
||||||
public virtual void Destroy() {{ OnDestroy?.Invoke(this); }}
|
public virtual void Destroy() {{ OnDestroy?.Invoke(this); }}
|
||||||
";
|
";
|
||||||
|
|
||||||
if (!ci.HasTrigger)
|
if (!ci.HasTrigger)
|
||||||
code +=
|
code += "\tpublic virtual AsyncReply<bool> Trigger(ResourceTrigger trigger) => new AsyncReply<bool>(true);\r\n\r\n";
|
||||||
"\tpublic virtual AsyncReply<bool> Trigger(ResourceTrigger trigger) => new AsyncReply<bool>(true);\r\n\r\n";
|
}
|
||||||
|
|
||||||
|
foreach (var f in ci.Fields)
|
||||||
|
{
|
||||||
|
var givenName = f.GetAttributes().FirstOrDefault(x => x.AttributeClass?.Name == "ExportAttribute")?.ConstructorArguments.FirstOrDefault().Value as string;
|
||||||
|
|
||||||
|
var fn = f.Name;
|
||||||
|
var pn = string.IsNullOrEmpty(givenName) ? SuggestExportName(fn) : givenName;
|
||||||
|
|
||||||
|
var attrs = string.Join("\r\n\t", f.GetAttributes().Select(x => FormatAttribute(x)));
|
||||||
|
|
||||||
|
if (f.Type.Name.StartsWith("ResourceEventHandler") || f.Type.Name.StartsWith("CustomResourceEventHandler"))
|
||||||
|
{
|
||||||
|
code += $"\t{attrs}\r\n\t public event {f.Type} {pn};\r\n";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
code += $"\t{attrs}\r\n\t public {f.Type} {pn} {{ \r\n\t\t get => {fn}; \r\n\t\t set {{ \r\n\t\t this.{fn} = value; \r\n\t\t Instance?.Modified(); \r\n\t\t}}\r\n\t}}\r\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
code += "}}\r\n";
|
||||||
|
|
||||||
|
spc.AddSource(ci.Name + ".g.cs", code);
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
//Debugger.Launch();
|
|
||||||
|
|
||||||
foreach (var f in ci.Fields)
|
|
||||||
{
|
{
|
||||||
var givenName = f.GetAttributes().Where(x => x.AttributeClass.Name == "ExportAttribute").FirstOrDefault()?.ConstructorArguments.FirstOrDefault().Value as string;
|
spc.AddSource(ci.Name + ".Error.g.cs", $"/*\r\n{ex}\r\n*/");
|
||||||
|
|
||||||
var fn = f.Name;
|
|
||||||
var pn = string.IsNullOrEmpty(givenName) ? SuggestExportName(fn) : givenName;
|
|
||||||
|
|
||||||
// copy attributes
|
|
||||||
//Debugger.Launch();
|
|
||||||
|
|
||||||
var attrs = string.Join("\r\n\t", f.GetAttributes().Select(x => FormatAttribute(x)));
|
|
||||||
|
|
||||||
//Debugger.Launch();
|
|
||||||
if (f.Type.Name.StartsWith("ResourceEventHandler") || f.Type.Name.StartsWith("CustomResourceEventHandler"))
|
|
||||||
{
|
|
||||||
code += $"\t{attrs}\r\n\t public event {f.Type} {pn};\r\n";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
code += $"\t{attrs}\r\n\t public {f.Type} {pn} {{ \r\n\t\t get => {fn}; \r\n\t\t set {{ \r\n\t\t this.{fn} = value; \r\n\t\t Instance?.Modified(); \r\n\t\t}}\r\n\t}}\r\n";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
code += "}}\r\n";
|
|
||||||
|
|
||||||
context.AddSource(ci.Name + ".g.cs", code);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// === Analysis ===
|
||||||
|
private static PerClass? AnalyzeClass(GeneratorSyntaxContext ctx)
|
||||||
|
{
|
||||||
|
var cds = (ClassDeclarationSyntax)ctx.Node;
|
||||||
|
var cls = ctx.SemanticModel.GetDeclaredSymbol(cds) as ITypeSymbol;
|
||||||
|
if (cls is null) return null;
|
||||||
|
|
||||||
|
var attrs = cls.GetAttributes();
|
||||||
|
|
||||||
|
// Collect ImportAttribute URLs
|
||||||
|
var importUrls = attrs
|
||||||
|
.Where(a => a.AttributeClass?.ToDisplayString() == "Esiur.Resource.ImportAttribute")
|
||||||
|
.SelectMany(a => a.ConstructorArguments.Select(x => x.Value?.ToString()))
|
||||||
|
.Where(s => !string.IsNullOrWhiteSpace(s))
|
||||||
|
.Cast<string>()
|
||||||
|
.ToImmutableArray();
|
||||||
|
|
||||||
|
// If class has ResourceAttribute, gather details
|
||||||
|
var hasResource = attrs.Any(a => a.AttributeClass?.ToDisplayString() == "Esiur.Resource.ResourceAttribute");
|
||||||
|
ResourceClassInfo? classInfo = null;
|
||||||
|
if (hasResource)
|
||||||
|
{
|
||||||
|
bool hasTrigger = cds.Members
|
||||||
|
.OfType<MethodDeclarationSyntax>()
|
||||||
|
.Select(m => ctx.SemanticModel.GetDeclaredSymbol(m) as IMethodSymbol)
|
||||||
|
.Where(s => s is not null)
|
||||||
|
.Any(s => s!.Name == "Trigger" && s.Parameters.Length == 1 && s.Parameters[0].Type.ToDisplayString() == "Esiur.Resource.ResourceTrigger");
|
||||||
|
|
||||||
|
var exportedFields = cds.Members
|
||||||
|
.OfType<FieldDeclarationSyntax>()
|
||||||
|
.SelectMany(f => f.Declaration.Variables.Select(v => (f, v)))
|
||||||
|
.Select(t => ctx.SemanticModel.GetDeclaredSymbol(t.v) as IFieldSymbol)
|
||||||
|
.Where(f => f is not null && !f!.IsConst)
|
||||||
|
.Where(f => f!.GetAttributes().Any(a => a.AttributeClass?.ToDisplayString() == "Esiur.Resource.ExportAttribute"))
|
||||||
|
.Cast<IFieldSymbol>()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
bool hasInterface = cls.AllInterfaces.Any(x => x.ToDisplayString() == "Esiur.Resource.IResource");
|
||||||
|
|
||||||
|
var key = $"{cls.ContainingAssembly.Name}:{cls.ContainingNamespace.ToDisplayString()}.{cls.Name}";
|
||||||
|
|
||||||
|
classInfo = new ResourceClassInfo
|
||||||
|
(
|
||||||
|
Key: key,
|
||||||
|
Name: cls.Name,
|
||||||
|
ClassDeclaration: cds,
|
||||||
|
ClassSymbol: cls,
|
||||||
|
Fields: exportedFields,
|
||||||
|
HasInterface: hasInterface,
|
||||||
|
HasTrigger: hasTrigger
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new PerClass(importUrls, classInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ImmutableArray<ResourceClassInfo> MergePartials(ImmutableArray<ResourceClassInfo> list)
|
||||||
|
{
|
||||||
|
var byKey = new Dictionary<string, ResourceClassInfo>(StringComparer.Ordinal);
|
||||||
|
foreach (var item in list)
|
||||||
|
{
|
||||||
|
if (byKey.TryGetValue(item.Key, out var existing))
|
||||||
{
|
{
|
||||||
context.AddSource(ci.Name + ".Error.g.cs", $"/*\r\n{ex}\r\n*/");
|
// merge fields + flags
|
||||||
|
var mergedFields = existing.Fields.Concat(item.Fields).ToList();
|
||||||
|
byKey[item.Key] = existing with
|
||||||
|
{
|
||||||
|
Fields = mergedFields,
|
||||||
|
HasInterface = existing.HasInterface || item.HasInterface,
|
||||||
|
HasTrigger = existing.HasTrigger || item.HasTrigger
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
byKey[item.Key] = item with { Fields = item.Fields.ToList() };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return byKey.Values.ToImmutableArray();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
|
// Determine if the base already implements IResource (either directly or via another generated part)
|
||||||
|
private static bool IsInterfaceImplemented(ResourceClassInfo ci, ImmutableArray<ResourceClassInfo> merged)
|
||||||
{
|
{
|
||||||
|
if (ci.HasInterface) return true;
|
||||||
context.AddSource("Error.g.cs", $"/*\r\n{ex}\r\n*/");
|
var baseType = ci.ClassSymbol.BaseType;
|
||||||
|
if (baseType is null) return false;
|
||||||
|
var baseKey = $"{baseType.ContainingAssembly.Name}:{baseType.ContainingNamespace.ToDisplayString()}.{baseType.Name}";
|
||||||
|
return merged.Any(x => x.Key == baseKey);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
// === Emission helpers (ported from your original generator) ===
|
||||||
{
|
private static void EmitTemplates(SourceProductionContext spc, TypeTemplate[] templates)
|
||||||
context.RegisterForSyntaxNotifications(() => new ResourceGeneratorReceiver());
|
{
|
||||||
|
foreach (var tmp in templates)
|
||||||
|
{
|
||||||
|
if (tmp.Type == TemplateType.Resource)
|
||||||
|
{
|
||||||
|
var source = TemplateGenerator.GenerateClass(tmp, templates, false);
|
||||||
|
spc.AddSource(tmp.ClassName + ".Generated.cs", source);
|
||||||
|
}
|
||||||
|
else if (tmp.Type == TemplateType.Record)
|
||||||
|
{
|
||||||
|
var source = TemplateGenerator.GenerateRecord(tmp, templates);
|
||||||
|
spc.AddSource(tmp.ClassName + ".Generated.cs", source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var typesFile = "using System; \r\n namespace Esiur { public static class Generated { public static Type[] Resources {get;} = new Type[] { " +
|
||||||
|
string.Join(",", templates.Where(x => x.Type == TemplateType.Resource).Select(x => $"typeof({x.ClassName})"))
|
||||||
|
+ " }; \r\n public static Type[] Records { get; } = new Type[] { " +
|
||||||
|
string.Join(",", templates.Where(x => x.Type == TemplateType.Record).Select(x => $"typeof({x.ClassName})"))
|
||||||
|
+ " }; " +
|
||||||
|
|
||||||
|
"\r\n } \r\n}";
|
||||||
|
|
||||||
|
spc.AddSource("Esiur.Generated.cs", typesFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void Report(SourceProductionContext ctx, string title, string message, DiagnosticSeverity severity)
|
||||||
|
{
|
||||||
|
var descriptor = new DiagnosticDescriptor("ESIUR001", title, message, "Esiur", severity, true);
|
||||||
|
ctx.ReportDiagnostic(Diagnostic.Create(descriptor, Location.None));
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Formatting helpers from your original code ===
|
||||||
|
private static string SuggestExportName(string fieldName)
|
||||||
|
{
|
||||||
|
if (char.IsUpper(fieldName[0]))
|
||||||
|
return fieldName.Substring(0, 1).ToLowerInvariant() + fieldName.Substring(1);
|
||||||
|
else
|
||||||
|
return char.ToUpperInvariant(fieldName[0]) + fieldName.Substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormatAttribute(AttributeData attribute)
|
||||||
|
{
|
||||||
|
if (attribute.AttributeClass is null)
|
||||||
|
throw new Exception("AttributeClass not found");
|
||||||
|
|
||||||
|
var className = attribute.AttributeClass.ToDisplayString();
|
||||||
|
if (!attribute.ConstructorArguments.Any() & !attribute.NamedArguments.Any())
|
||||||
|
return $"[{className}]";
|
||||||
|
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.Append('[').Append(className).Append('(');
|
||||||
|
if (attribute.ConstructorArguments.Any())
|
||||||
|
sb.Append(string.Join(", ", attribute.ConstructorArguments.Select(FormatConstant)));
|
||||||
|
if (attribute.NamedArguments.Any())
|
||||||
|
{
|
||||||
|
if (attribute.ConstructorArguments.Any()) sb.Append(", ");
|
||||||
|
sb.Append(string.Join(", ", attribute.NamedArguments.Select(na => $"{na.Key} = {FormatConstant(na.Value)}")));
|
||||||
|
}
|
||||||
|
sb.Append(")] ");
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormatConstant(TypedConstant constant)
|
||||||
|
{
|
||||||
|
if (constant.Kind == TypedConstantKind.Array)
|
||||||
|
return $"new {constant.Type?.ToDisplayString()} {{{string.Join(", ", constant.Values.Select(FormatConstant))}}}";
|
||||||
|
return constant.ToCSharpString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Data carriers for the pipeline ===
|
||||||
|
private readonly record struct PerClass(
|
||||||
|
ImmutableArray<string> ImportUrls,
|
||||||
|
ResourceClassInfo? ClassInfo
|
||||||
|
);
|
||||||
|
|
||||||
|
private sealed record ResourceClassInfo(
|
||||||
|
string Key,
|
||||||
|
string Name,
|
||||||
|
ClassDeclarationSyntax ClassDeclaration,
|
||||||
|
ITypeSymbol ClassSymbol,
|
||||||
|
List<IFieldSymbol> Fields,
|
||||||
|
bool HasInterface,
|
||||||
|
bool HasTrigger
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -4,25 +4,17 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
|
|
||||||
namespace Esiur.Proxy;
|
namespace Esiur.Proxy;
|
||||||
public struct ResourceGeneratorClassInfo
|
public struct ResourceGeneratorClassInfo
|
||||||
{
|
{
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public bool HasInterface { get; set; }
|
public bool HasInterface { get; set; }
|
||||||
|
|
||||||
public bool HasTrigger { get; set; }
|
public bool HasTrigger { get; set; }
|
||||||
public List<IFieldSymbol> Fields { get; set; }
|
public List<IFieldSymbol> Fields { get; set; }
|
||||||
public ITypeSymbol ClassSymbol { get; set; }
|
public ITypeSymbol ClassSymbol { get; set; }
|
||||||
|
|
||||||
public ClassDeclarationSyntax ClassDeclaration { get; set; }
|
public ClassDeclarationSyntax ClassDeclaration { get; set; }
|
||||||
|
|
||||||
public bool IsInterfaceImplemented(Dictionary<string, ResourceGeneratorClassInfo> classes)
|
// Deprecated in incremental path. Use IsInterfaceImplemented(ResourceClassInfo, merged) instead.
|
||||||
{
|
public bool IsInterfaceImplemented(System.Collections.Generic.Dictionary<string, ResourceGeneratorClassInfo> classes) => HasInterface;
|
||||||
if (HasInterface)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
// Are we going to generate the interface for the parent ?
|
|
||||||
var fullName = ClassSymbol.BaseType.ContainingAssembly + "." + ClassSymbol.BaseType.Name;
|
|
||||||
return classes.ContainsKey(fullName);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user