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 ;
using System.Linq ;
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 const string Namespace = "mROA.Implementation" ;
private const string AttributeName = "SharedObjectInterafceAttribute" ;
2025-02-04 19:02:39 +03:00
2025-02-20 16:10:09 +03:00
private const string AttributeSourceCode = $@"// <auto-generated/>
2025-02-04 19:02:39 +03:00
namespace {Namespace}
{{
[System.AttributeUsage(System.AttributeTargets.Class)]
public class {AttributeName} : System.Attribute
{{
}}
}}" ;
2025-02-20 16:10:09 +03:00
/// <summary>
/// Generate code action.
/// It will be executed on specific nodes (ClassDeclarationSyntax annotated with the [Report] attribute) changed by the user.
/// </summary>
/// <param name="context">Source generation context used to add source files.</param>
/// <param name="compilation">Compilation used to provide access to the Semantic Model.</param>
/// <param name="classes">Nodes annotated with the [Report] attribute that trigger the generate action.</param>
private void GenerateCode ( GeneratorExecutionContext context , Compilation compilation ,
ImmutableArray < InterfaceDeclarationSyntax > classes )
{
var methods = new List <( string , IMethodSymbol )>();
var frontendContextRepo = new List < string >();
// Go through all filtered class declarations.
var declarations = classes . ToList (). OrderBy ( i => i . Identifier . Text ). ToList ();
foreach ( var classDeclarationSyntax in declarations )
{
// We need to get semantic model of the class to retrieve metadata.
var semanticModel = compilation . GetSemanticModel ( classDeclarationSyntax . SyntaxTree );
2025-02-04 19:02:39 +03:00
2025-02-20 16:10:09 +03:00
// Symbols allow us to get the compile-time information.
if ( semanticModel . GetDeclaredSymbol ( classDeclarationSyntax ) is not INamedTypeSymbol classSymbol )
continue ;
2025-02-04 19:02:39 +03:00
2025-02-20 16:10:09 +03:00
var namespaceName = classSymbol . ContainingNamespace . ToDisplayString ();
2025-02-04 19:02:39 +03:00
2025-03-01 15:11:11 +03:00
2025-02-20 16:10:09 +03:00
// 'Identifier' means the token of the node. Get class name from the syntax node.
var className = classDeclarationSyntax . Identifier . Text ;
2025-02-04 19:02:39 +03:00
2025-02-20 16:10:09 +03:00
// Go through all class members with a particular type (property) to generate method lines.
var methodBody = classSymbol . GetMembers ()
. OfType < IMethodSymbol >(). OrderBy ( i => i . Name );
2025-02-04 19:02:39 +03:00
2025-02-20 16:10:09 +03:00
var originalName = className ;
// Build up the source code
className = className . TrimStart ( 'I' ) + "RemoteEndpoint" ;
2025-02-04 22:10:48 +03:00
2025-02-04 19:02:39 +03:00
2025-02-20 16:10:09 +03:00
var methodsText = new List < string >();
2025-03-01 15:11:11 +03:00
2025-02-20 16:10:09 +03:00
foreach ( var method in methodBody )
{
var index = methods . Count ;
methods . Add (( namespaceName + "." + originalName , method ));
var sb = new StringBuilder ();
2025-02-04 19:02:39 +03:00
2025-02-20 16:10:09 +03:00
bool isAsync = method . ReturnType . Name == "Task" ;
2025-02-22 09:28:59 +03:00
bool isVoid = method . ReturnType . Name == "Void" ||
method . ReturnType . ToString () == "System.Threading.Tasks.Task" ;
2025-02-20 16:10:09 +03:00
bool isParametrized = method . Parameters . Length == 1 && ! isAsync ||
method . Parameters . Length == 2 && isAsync ;
2025-02-04 19:02:39 +03:00
2025-02-04 23:19:24 +03:00
2025-02-20 16:10:09 +03:00
//Creating signature
sb . AppendLine ( "public" + ( isAsync
? " async "
: " " ) +
$"{method.ReturnType.ToDisplayString()} {method.Name}({string.Join(" , ", method.Parameters.Select(ToFullString))}){{" );
2025-02-04 23:19:24 +03:00
2025-02-20 16:10:09 +03:00
var prefix = isAsync ? "await " : "" ;
var postfix = ! isAsync ? ( isVoid ? ".Wait()" : ".GetAwaiter().GetResult()" ) : "" ;
var parameterLink = isParametrized ? ", " + method . Parameters . First (). Name : string . Empty ;
2025-02-22 09:28:59 +03:00
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})" ;
2025-02-04 23:19:24 +03:00
2025-02-20 16:10:09 +03:00
if (! isVoid )
prefix = "return " + prefix ;
sb . AppendLine ( "\t\t\t" + prefix + caller + postfix + ";" );
sb . AppendLine ( "\t\t}" );
methodsText . Add ( sb . ToString ());
}
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-02-20 16:10:09 +03:00
{string.Join(" \ r \ n \ t ", methodsText)}
}}
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.
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}) }}" );
}
if ( methods . Count != 0 )
{
2025-03-01 15:11:11 +03:00
// var methodsStringed = methods.Select(i =>
// $"typeof({i.Item1}).GetMethod(\"{i.Item2.Name}\", new Type[] {{{string.Join(", ", i.Item2.Parameters.Select(p => $"typeof({p.Type.ToDisplayString()})"))}}})")
// .ToList();
2025-02-20 16:10:09 +03:00
var methodsStringed = methods . Select ( i =>
2025-03-01 15:11:11 +03:00
$"typeof({i.Item1}).GetMethod(\" { i . Item2 . Name } \ ")" )
2025-02-20 16:10:09 +03:00
. ToList ();
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-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-02-20 16:10:09 +03:00
private readonly List<MethodInfo> _methods = new () {{
{string.Join(" , \ r \ n \ t \ t \ t ", methodsStringed)}
}};
public MethodInfo GetMethod ( int id )
{{
2025-02-23 10:18:38 +03:00
if ( id == - 1 )
return typeof ( IDisposable ). GetMethod ( "" Dispose "" );
2025-02-20 16:10:09 +03:00
if ( _methods . Count <= id )
return null ;
return _methods [ id ];
}}
public int RegisterMethod ( MethodInfo method )
{{
_methods . Add ( method );
return _methods . Count - 1 ;
}}
public IEnumerable < MethodInfo > GetMethods ()
{{
return _methods ;
}}
public void Inject < T >( T dependency )
{{
}}
2025-02-04 22:10:48 +03:00
}}
2025-02-20 16:10:09 +03:00
}}
";
context . AddSource ( $"CoCodegenMethodRepository.g.cs" , SourceText . From ( coCodegenRepoCode , Encoding . UTF8 ));
}
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-02-20 16:10:09 +03:00
context . AddSource ( "RemoteTypeBinder.g.cs" , SourceText . From ( fronendRepoCode , Encoding . UTF8 ));
}
2025-02-06 09:18:52 +03:00
}
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-02-20 16:10:09 +03:00
var type = taskType . ToString ();
type = type . Substring ( type . IndexOf ( '<' ) + 1 );
return type . Substring ( 0 , type . Length - 1 );
}
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 )
{
foreach ( AttributeListSyntax attributeListSyntax in attributes )
foreach ( AttributeSyntax attributeSyntax in attributeListSyntax . Attributes )
{
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-02-18 00:10:53 +03:00
}
2025-02-04 19:02:39 +03:00
}