2025-02-22 09:28:59 +03:00
using System ;
2025-02-04 22:10:48 +03:00
using System.Collections.Generic ;
2025-02-04 19:02:39 +03:00
using System.Collections.Immutable ;
2025-03-07 09:01:21 +03:00
using System.IO ;
2025-02-04 19:02:39 +03:00
using System.Linq ;
2025-03-07 09:01:21 +03:00
using System.Reflection ;
2025-02-04 19:02:39 +03:00
using System.Text ;
using Microsoft.CodeAnalysis ;
using Microsoft.CodeAnalysis.CSharp.Syntax ;
using Microsoft.CodeAnalysis.Text ;
2025-02-20 16:10:09 +03:00
namespace mROA.Codegen
2025-02-04 19:02:39 +03:00
{
2025-02-20 16:10:09 +03:00
/// <summary>
/// A sample source generator that creates a custom report based on class properties. The target class should be annotated with the 'Generators.ReportAttribute' attribute.
/// When using the source code as a baseline, an incremental source generator is preferable because it reduces the performance overhead.
/// </summary>
[Generator]
public class mROASourceGenerator : ISourceGenerator
{
private void GenerateCode ( GeneratorExecutionContext context , Compilation compilation ,
ImmutableArray < InterfaceDeclarationSyntax > classes )
{
2025-03-07 09:01:21 +03:00
// For future
// var asm = Assembly.GetAssembly(typeof(mROASourceGenerator));
// var files = asm.GetManifestResourceNames();
// var test = asm.GetManifestResourceStream("mROA.Codegen.test.tpt");
// var reader = new StreamReader(test);
// var allText = reader.ReadToEnd();
2025-02-20 16:10:09 +03:00
var frontendContextRepo = new List < string >();
2025-03-06 14:10:52 +03:00
2025-03-07 15:46:22 +03:00
List < IMethodSymbol > innerMethods = new List < IMethodSymbol >();
2025-02-20 16:10:09 +03:00
var declarations = classes . ToList (). OrderBy ( i => i . Identifier . Text ). ToList ();
foreach ( var classDeclarationSyntax in declarations )
2025-03-07 09:01:21 +03:00
{
2025-02-20 16:10:09 +03:00
var semanticModel = compilation . GetSemanticModel ( classDeclarationSyntax . SyntaxTree );
2025-02-04 19:02:39 +03:00
2025-02-20 16:10:09 +03:00
if ( semanticModel . GetDeclaredSymbol ( classDeclarationSyntax ) is not INamedTypeSymbol classSymbol )
continue ;
2025-03-07 09:01:21 +03:00
2025-03-06 14:10:52 +03:00
var namespaceName = classSymbol . ContainingNamespace . ToDisplayString ();
2025-02-20 16:10:09 +03:00
var className = classDeclarationSyntax . Identifier . Text ;
2025-03-07 15:46:22 +03:00
innerMethods = CollectMembers ( classSymbol )
. Where ( i => i . MethodKind != MethodKind . EventAdd || i . MethodKind != MethodKind . EventRemove )
. ToList ();
var associated = innerMethods . Select ( i => i . AssociatedSymbol as IPropertySymbol ). Where ( i => i != null )
. Select ( i => i !). Distinct ( SymbolEqualityComparer . Default ). Cast < IPropertySymbol >(). ToList ();
2025-02-20 16:10:09 +03:00
var originalName = className ;
2025-02-04 22:10:48 +03:00
2025-03-07 09:01:21 +03:00
className = className . TrimStart ( 'I' ) + "RemoteEndpoint" ;
2025-03-06 14:10:52 +03:00
2025-02-04 19:02:39 +03:00
2025-03-07 15:46:22 +03:00
var declaredMethods = new List < string >();
var propertiesAccessMethods = new List <( string , IMethodSymbol )>();
var propertiesImplementations = new List < string >( associated . Count );
foreach ( var method in innerMethods )
{
switch ( method . MethodKind )
2025-03-06 14:10:52 +03:00
{
2025-03-07 15:46:22 +03:00
case MethodKind . PropertyGet or MethodKind . PropertySet :
GeneratePropertyMethod ( method , innerMethods , propertiesAccessMethods );
2025-03-06 14:10:52 +03:00
continue ;
2025-03-07 15:46:22 +03:00
default :
GenerateDeclaretedMethod ( method , declaredMethods , innerMethods );
break ;
2025-03-06 14:10:52 +03:00
}
2025-03-07 15:46:22 +03:00
}
2025-02-04 23:19:24 +03:00
2025-03-07 15:46:22 +03:00
foreach ( var prop in associated )
{
var start = $"public {prop.Type.ToDisplayString()} {prop.Name}" ;
2025-02-20 16:10:09 +03:00
}
var code = $@"// <auto-generated/>
2025-02-04 19:02:39 +03:00
2025-02-05 14:50:07 +03:00
using mROA;
2025-02-04 19:02:39 +03:00
using System;
2025-02-04 20:57:04 +03:00
using mROA.Implementation;
2025-02-04 19:02:39 +03:00
using System.Collections.Generic;
2025-02-07 17:11:58 +03:00
using mROA.Abstract;
2025-02-04 19:02:39 +03:00
2025-02-20 16:10:09 +03:00
namespace {namespaceName}
2025-02-04 19:02:39 +03:00
{{
2025-02-20 16:10:09 +03:00
partial class {className} : RemoteObjectBase, {originalName}
2025-02-18 00:10:53 +03:00
{{
2025-02-20 16:10:09 +03:00
public {className}(int id, IRepresentationModule representationModule) : base(id, representationModule)
{{
}}
2025-02-04 23:19:24 +03:00
2025-03-07 15:46:22 +03:00
{string.Join(" \ r \ n \ t ", declaredMethods)}
2025-02-20 16:10:09 +03:00
}}
2025-02-04 19:02:39 +03:00
}}
";
2025-02-04 22:10:48 +03:00
2025-02-20 16:10:09 +03:00
// Add the source code to the compilation.
2025-03-06 14:10:52 +03:00
// context.AddSource($"{className}.g.cs", SourceText.From(code, Encoding.UTF8));
2025-02-04 22:10:48 +03:00
2025-02-20 16:10:09 +03:00
frontendContextRepo . Add (
$"{{ typeof({classSymbol.ToDisplayString()}), typeof({namespaceName}.{className}) }}" );
}
2025-03-07 15:46:22 +03:00
if ( innerMethods . Count != 0 )
2025-02-20 16:10:09 +03:00
{
2025-03-06 14:10:52 +03:00
var methodsStringed = Array . Empty < string >();
2025-02-15 09:06:29 +03:00
2025-02-20 16:10:09 +03:00
var coCodegenRepoCode = @ $"// <auto-generated/>
2025-02-04 22:10:48 +03:00
using System.Collections.Generic;
using System.Reflection;
2025-02-07 17:11:58 +03:00
using mROA.Abstract;
2025-03-06 14:10:52 +03:00
using mROA.Implementation;
2025-02-20 16:10:09 +03:00
using System;
2025-02-04 22:10:48 +03:00
2025-02-20 16:10:09 +03:00
namespace mROA.Codegen
2025-02-04 22:10:48 +03:00
{{
2025-02-20 16:10:09 +03:00
public class CoCodegenMethodRepository : IMethodRepository
2025-02-04 22:10:48 +03:00
{{
2025-03-06 14:10:52 +03:00
private readonly List<IMethodInvoker> _methods = new () {{
2025-02-20 16:10:09 +03:00
{string.Join(" , \ r \ n \ t \ t \ t ", methodsStringed)}
}};
2025-03-06 14:10:52 +03:00
public IMethodInvoker GetMethod ( int id )
2025-02-20 16:10:09 +03:00
{{
2025-02-23 10:18:38 +03:00
if ( id == - 1 )
2025-03-06 14:10:52 +03:00
return mROA . Implementation . MethodInvoker . Dispose ;
2025-02-23 10:18:38 +03:00
2025-02-20 16:10:09 +03:00
if ( _methods . Count <= id )
return null ;
return _methods [ id ];
}}
public void Inject < T >( T dependency )
{{
}}
2025-02-04 22:10:48 +03:00
}}
2025-02-20 16:10:09 +03:00
}}
";
2025-03-06 14:10:52 +03:00
context . AddSource ( "CoCodegenMethodRepository.g.cs" , SourceText . From ( coCodegenRepoCode , Encoding . UTF8 ));
2025-02-20 16:10:09 +03:00
}
2025-02-22 09:28:59 +03:00
2025-02-20 16:10:09 +03:00
if ( frontendContextRepo . Count != 0 )
{
var fronendRepoCode = @ $"// <auto-generated/>
using mROA.Implementation;
using mROA.Abstract;
using System.Collections.Generic;
using System;
using System.Reflection;
2025-02-05 10:58:50 +03:00
2025-02-20 16:10:09 +03:00
namespace mROA.Codegen
{{
public sealed class RemoteTypeBinder
2025-02-05 10:58:50 +03:00
{{
2025-02-20 16:10:09 +03:00
static RemoteTypeBinder(){{
RemoteContextRepository.RemoteTypes = new Dictionary<Type, Type> {{
{string.Join(" , \ r \ n \ t \ t \ t ", frontendContextRepo)}}};
}}
2025-02-05 10:58:50 +03:00
}}
2025-02-04 22:10:48 +03:00
}}
";
2025-03-06 14:10:52 +03:00
// context.AddSource("RemoteTypeBinder.g.cs", SourceText.From(fronendRepoCode, Encoding.UTF8));
2025-02-20 16:10:09 +03:00
}
2025-02-06 09:18:52 +03:00
}
2025-03-07 15:46:22 +03:00
private void GenerateDeclaretedMethod ( IMethodSymbol method , List < string > declaretedMethods ,
List < IMethodSymbol > methods )
{
var index = methods . IndexOf ( method );
var sb = new StringBuilder ();
bool isParametrized ;
bool isAsync ;
bool isVoid ;
List < IParameterSymbol >? parameters ;
switch ( method . ReturnType )
{
case INamedTypeSymbol namedType :
isAsync = namedType . Name == "Task" ;
isVoid = isAsync && namedType . TypeParameters . Length == 0 || namedType . Name == "Void" ;
parameters = method . Parameters . ToList ();
parameters . RemoveAll ( i => i . Type . Name is "CancellationToken" or "RequestContext" );
isParametrized = parameters . Count != 0 ;
break ;
case IArrayTypeSymbol :
isAsync = false ;
isVoid = false ;
isParametrized = method . Parameters . Length != 0 ;
break ;
default :
return ;
}
//Creating signature
sb . AppendLine ( "public" + ( isAsync
? " async "
: " " ) +
$"{method.ReturnType.ToDisplayString()} {method.Name}({string.Join(" , ", method.Parameters.Select(ToFullString))}){{" );
var prefix = isAsync ? "await " : "" ;
var postfix = ! isAsync ? isVoid ? ".Wait()" : ".GetAwaiter().GetResult()" : "" ;
var parameterLink = isParametrized
? ", new object[] {" + string . Join ( ", " , method . Parameters . Select ( i => i . Name )) + "}"
: string . Empty ;
var tokenInsert = isAsync
? isParametrized
? ", cancellationToken : " + method . Parameters [ 1 ]. Name
: ", cancellationToken : " + method . Parameters [ 0 ]. Name
: String . Empty ;
var caller = isVoid
? $"CallAsync({index}{parameterLink}{tokenInsert})"
: isAsync
? $"GetResultAsync<{ExtractTaskType(method.ReturnType)}>({index}{parameterLink}{tokenInsert})"
: $"GetResultAsync<{ToFullString(method.ReturnType)}>({index}{parameterLink}{tokenInsert})" ;
if (! isVoid )
prefix = "return " + prefix ;
sb . AppendLine ( "\t\t\t" + prefix + caller + postfix + ";" );
sb . AppendLine ( "\t\t}" );
declaretedMethods . Add ( sb . ToString ());
}
public void GeneratePropertyMethod ( IMethodSymbol method , List < IMethodSymbol > methods ,
List <( string , IMethodSymbol )> propsCollection )
{
var index = methods . IndexOf ( method );
var sb = new StringBuilder ();
if ( method . MethodKind == MethodKind . PropertyGet )
{
var parametersArray = "" ;
if ( method . Parameters . Length != 0 )
{
parametersArray = $", new object[] {{{string.Join(" , ", method.Parameters.Select(p => p.Name))}}}" ;
}
sb . AppendLine (
$"get => GetResultAsync<{method.ReturnType.ToDisplayString()}>({index}{parametersArray}).Wait();" );
}
else
{
var parametersArray = "" ;
if ( method . Parameters . Length != 0 )
{
parametersArray = ", " + string . Join ( ", " , method . Parameters . Select ( p => p . Name ));
}
sb . AppendLine (
$"set => CallAsync({index}, new object[] {{ value{parametersArray} }}).GetAwaiter().GetResult();" );
}
propsCollection . Add (( sb . ToString (), method ));
}
2025-02-20 16:10:09 +03:00
private static string ToFullString ( IParameterSymbol parameter )
=> /*parameter.Type.ContainingNamespace is null*/
/*?*/ parameter . ToDisplayString ();
/*: $"{parameter.Type.ContainingNamespace.ToDisplayString()}.{parameter.Type.MetadataName} {parameter.Name}";*/
private static string ToFullString ( ITypeSymbol type ) =>
// => type.ContainingNamespace is null || type.Name == "Void"
type . ToDisplayString ();
// : $"{type.ContainingNamespace.ToDisplayString()}.{type.MetadataName}";
private string ExtractTaskType ( ITypeSymbol taskType )
2025-02-06 09:18:52 +03:00
{
2025-03-06 14:10:52 +03:00
return ( taskType as INamedTypeSymbol ). TypeParameters [ 0 ]. ToDisplayString ();
2025-02-20 16:10:09 +03:00
}
2025-02-05 00:26:28 +03:00
2025-02-20 16:10:09 +03:00
public void Initialize ( GeneratorInitializationContext context )
{
}
2025-02-05 00:26:28 +03:00
2025-02-20 16:10:09 +03:00
public void Execute ( GeneratorExecutionContext context )
{
var trees = context . Compilation . SyntaxTrees ;
var interfaces = new List < InterfaceDeclarationSyntax >();
foreach ( var tree in trees )
{
var node = tree . GetRoot () as CompilationUnitSyntax ;
foreach ( var member in node . Members )
{
if ( member is InterfaceDeclarationSyntax ids )
{
interfaces . Add ( ids );
}
else if ( member is NamespaceDeclarationSyntax nds )
{
foreach ( var inside in nds . Members )
if ( inside is InterfaceDeclarationSyntax ids2 )
if ( ContainsSOIAttribute ( ids2 . AttributeLists , context , ids2 ))
interfaces . Add ( ids2 );
}
}
}
GenerateCode ( context , context . Compilation , interfaces . ToImmutableArray ());
2025-02-06 09:18:52 +03:00
}
2025-02-22 09:28:59 +03:00
2025-02-20 16:10:09 +03:00
private bool ContainsSOIAttribute ( SyntaxList < AttributeListSyntax > attributes , GeneratorExecutionContext context ,
InterfaceDeclarationSyntax interfaceDeclarationSyntax )
{
2025-03-07 09:01:21 +03:00
foreach ( var attributeSyntax in
attributes . SelectMany ( attributeListSyntax => attributeListSyntax . Attributes ))
2025-02-20 16:10:09 +03:00
{
if ( context . Compilation . GetSemanticModel ( interfaceDeclarationSyntax . SyntaxTree )
. GetSymbolInfo ( attributeSyntax ). Symbol is not IMethodSymbol attributeSymbol )
continue ; // if we can't get the symbol, ignore it
2025-02-18 00:10:53 +03:00
2025-02-20 16:10:09 +03:00
string attributeName = attributeSymbol . ContainingType . ToDisplayString ();
2025-02-18 00:10:53 +03:00
2025-02-20 16:10:09 +03:00
// Check the full name of the [Report] attribute.
if ( attributeName == "mROA.Implementation.Attributes.SharedObjectInterfaceAttribute" )
return true ;
}
2025-02-18 00:10:53 +03:00
2025-02-20 16:10:09 +03:00
return false ;
}
2025-03-06 14:10:52 +03:00
2025-03-07 09:01:21 +03:00
private List < IMethodSymbol > CollectMembers ( INamedTypeSymbol type )
2025-03-06 14:10:52 +03:00
{
var methods = type . GetMembers (). OfType < IMethodSymbol >(). ToList ();
foreach ( var inner in type . AllInterfaces )
{
methods . AddRange ( inner . GetMembers (). OfType < IMethodSymbol >());
}
2025-03-07 09:01:21 +03:00
methods . RemoveAll ( m => m . Name == "Dispose" );
2025-03-06 14:10:52 +03:00
return methods . OrderBy ( i => i . Name ). ToList ();
}
2025-02-18 00:10:53 +03:00
}
2025-02-04 19:02:39 +03:00
}