2025-08-16 13:13:34 +04:00
using Microsoft.CodeAnalysis ;
using Microsoft.CodeAnalysis.CSharp ;
using Microsoft.CodeAnalysis.CSharp.Syntax ;
2026-03-06 20:01:54 +04:00
using Microsoft.CodeAnalysis.Text ;
2025-08-16 13:13:34 +04:00
using System.Collections.Immutable ;
2026-03-06 20:01:54 +04:00
using System.Text ;
2025-08-18 20:32:24 +04:00
2026-03-06 20:01:54 +04:00
namespace Telegrator.Analyzers ;
[Generator(LanguageNames.CSharp)]
public class GeneratedKeyboardMarkupGenerator : IIncrementalGenerator
2025-08-16 13:13:34 +04:00
{
2026-03-06 20:01:54 +04:00
// Return types
private const string InlineReturnType = "InlineKeyboardMarkup" ;
private const string ReplyReturnType = "ReplyKeyboardMarkup" ;
// Attribute names
private const string CallbackDataAttribute = "CallbackButton" ;
private const string CallbackGameAttribute = "GameButton" ;
private const string CopyTextAttribute = "CopyTextButton" ;
private const string LoginRequestAttribute = "LoginRequestButton" ;
private const string PayRequestAttribute = "PayRequestButton" ;
private const string SwitchQueryAttribute = "SwitchQueryButton" ;
private const string QueryChosenAttribute = "QueryChosenButton" ;
private const string QueryCurrentAttribute = "QueryCurrentButton" ;
private const string UrlRedirectAttribute = "UrlRedirectButton" ;
private const string RequestChatAttribute = "RequestChatButton" ;
private const string RequestContactAttribute = "RequestContactButton" ;
private const string RequestLocationAttribute = "RequestLocationButton" ;
private const string RequestPoolAttribute = "RequestPoolButton" ;
private const string RequestUsersAttribute = "RequestUsersButton" ;
private const string WebAppAttribute = "WebApp" ;
// Markup lists
private static readonly string [ ] InlineAttributes = [ CallbackDataAttribute , CallbackGameAttribute , CopyTextAttribute , LoginRequestAttribute , PayRequestAttribute , UrlRedirectAttribute , WebAppAttribute , SwitchQueryAttribute , QueryChosenAttribute , QueryCurrentAttribute ] ;
private static readonly string [ ] ReplyAttributes = [ RequestChatAttribute , RequestContactAttribute , RequestLocationAttribute , RequestPoolAttribute , RequestUsersAttribute , WebAppAttribute ] ;
// Usings
private static readonly string [ ] DefaultUsings = [ "Telegram.Bot.Types.ReplyMarkups" ] ;
// Markup layouts
private static readonly Dictionary < string , MemberAccessExpressionSyntax > InlineKeyboardLayout = new Dictionary < string , MemberAccessExpressionSyntax > ( )
2025-08-16 13:13:34 +04:00
{
2026-03-06 20:01:54 +04:00
{ CallbackDataAttribute , AccessExpression ( "InlineKeyboardButton" , "WithCallbackData" ) } ,
{ CallbackGameAttribute , AccessExpression ( "InlineKeyboardButton" , "WithCallbackGame" ) } ,
{ CopyTextAttribute , AccessExpression ( "InlineKeyboardButton" , "WithCopyText" ) } ,
{ LoginRequestAttribute , AccessExpression ( "InlineKeyboardButton" , "WithLoginUrl" ) } ,
{ PayRequestAttribute , AccessExpression ( "InlineKeyboardButton" , "WithPay" ) } ,
{ SwitchQueryAttribute , AccessExpression ( "InlineKeyboardButton" , "WithSwitchInlineQuery" ) } ,
{ QueryChosenAttribute , AccessExpression ( "InlineKeyboardButton" , "WithSwitchInlineQueryChosenChat" ) } ,
{ QueryCurrentAttribute , AccessExpression ( "InlineKeyboardButton" , "WithSwitchInlineQueryCurrentChat" ) } ,
{ UrlRedirectAttribute , AccessExpression ( "InlineKeyboardButton" , "WithUrl" ) } ,
{ WebAppAttribute , AccessExpression ( "InlineKeyboardButton" , "WithWebApp" ) } ,
} ;
private static readonly Dictionary < string , MemberAccessExpressionSyntax > ReplyKeyboardLayout = new Dictionary < string , MemberAccessExpressionSyntax > ( )
{
{ RequestChatAttribute , AccessExpression ( "KeyboardButton" , "WithRequestChat" ) } ,
{ RequestContactAttribute , AccessExpression ( "KeyboardButton" , "WithRequestContact" ) } ,
{ RequestLocationAttribute , AccessExpression ( "KeyboardButton" , "WithRequestLocation" ) } ,
{ RequestPoolAttribute , AccessExpression ( "KeyboardButton" , "WithRequestPoll" ) } ,
{ RequestUsersAttribute , AccessExpression ( "KeyboardButton" , "WithRequestUsers" ) } ,
{ WebAppAttribute , AccessExpression ( "KeyboardButton" , "WithWebApp" ) }
} ;
// Markup map
private static readonly Dictionary < string , Dictionary < string , MemberAccessExpressionSyntax > > LayoutNames = new Dictionary < string , Dictionary < string , MemberAccessExpressionSyntax > > ( )
{
{ InlineReturnType , InlineKeyboardLayout } ,
{ ReplyReturnType , ReplyKeyboardLayout }
} ;
// Diagnostic descriptors
private static readonly DiagnosticDescriptor WrongReturnType = new DiagnosticDescriptor ( "TLG201" , "Wrong return type" , string . Empty , "Telegrator.Modelling" , DiagnosticSeverity . Error , true ) ;
private static readonly DiagnosticDescriptor UnsupportedAttribute = new DiagnosticDescriptor ( "TLG202" , "Unsupported or invalid attribute" , string . Empty , "Telegrator.Modelling" , DiagnosticSeverity . Error , true ) ;
private static readonly DiagnosticDescriptor NotPartialMethod = new DiagnosticDescriptor ( "TLG203" , "Not a partial member" , string . Empty , "Telegrator.Modelling" , DiagnosticSeverity . Error , true ) ;
private static readonly DiagnosticDescriptor UseBodylessMethod = new DiagnosticDescriptor ( "TLG204" , "Use bodyless method" , string . Empty , "Telegrator.Modelling" , DiagnosticSeverity . Error , true ) ;
private static readonly DiagnosticDescriptor UseParametrlessMethod = new DiagnosticDescriptor ( "TLG205" , "Use parametrless method" , string . Empty , "Telegrator.Modelling" , DiagnosticSeverity . Error , true ) ;
private static readonly DiagnosticDescriptor UseGetOnlyProperty = new DiagnosticDescriptor ( "TLG206" , "Use property with only get accessor" , string . Empty , "Telegrator.Modelling" , DiagnosticSeverity . Error , true ) ;
private static readonly DiagnosticDescriptor UseBodylessGetAccessor = new DiagnosticDescriptor ( "TLG207" , "Use bodyless get accessor" , string . Empty , "Telegrator.Modelling" , DiagnosticSeverity . Error , true ) ;
public void Initialize ( IncrementalGeneratorInitializationContext context )
{
IncrementalValueProvider < ImmutableArray < MethodDeclarationSyntax > > methodsPipeline = context . SyntaxProvider . CreateSyntaxProvider ( ProvideMethods , TransformMethods ) . Where ( x = > x ! = null ) . Collect ( ) ;
IncrementalValueProvider < ImmutableArray < PropertyDeclarationSyntax > > propertiesPipeline = context . SyntaxProvider . CreateSyntaxProvider ( ProvideProperties , TransformProperties ) . Where ( x = > x ! = null ) . Collect ( ) ;
2025-08-18 20:32:24 +04:00
2026-03-06 20:01:54 +04:00
context . RegisterSourceOutput ( methodsPipeline , ExecuteMethodsPipeline ) ;
context . RegisterSourceOutput ( propertiesPipeline , ExecutePropertiesPipeline ) ;
}
2025-08-16 13:13:34 +04:00
2026-03-06 20:01:54 +04:00
private static bool ProvideMethods ( SyntaxNode syntaxNode , CancellationToken cancellationToken )
{
cancellationToken . ThrowIfCancellationRequested ( ) ;
if ( syntaxNode is not MethodDeclarationSyntax method )
return false ;
2025-08-16 13:13:34 +04:00
2026-03-06 20:01:54 +04:00
if ( ! HasGenAttributes ( method ) )
return false ;
2025-08-16 13:13:34 +04:00
2026-03-06 20:01:54 +04:00
return true ;
}
2025-08-16 13:13:34 +04:00
2026-03-06 20:01:54 +04:00
private static bool ProvideProperties ( SyntaxNode syntaxNode , CancellationToken cancellationToken )
{
cancellationToken . ThrowIfCancellationRequested ( ) ;
if ( syntaxNode is not PropertyDeclarationSyntax property )
return false ;
2025-08-19 01:39:09 +04:00
2026-03-06 20:01:54 +04:00
if ( ! HasGenAttributes ( property ) )
return false ;
2025-08-19 01:39:09 +04:00
2026-03-06 20:01:54 +04:00
return true ;
}
2025-08-19 01:39:09 +04:00
2026-03-06 20:01:54 +04:00
private static MethodDeclarationSyntax TransformMethods ( GeneratorSyntaxContext context , CancellationToken cancellationToken )
{
cancellationToken . ThrowIfCancellationRequested ( ) ;
return ( MethodDeclarationSyntax ) context . Node ;
}
2025-08-16 13:13:34 +04:00
2026-03-06 20:01:54 +04:00
private static PropertyDeclarationSyntax TransformProperties ( GeneratorSyntaxContext context , CancellationToken cancellationToken )
{
cancellationToken . ThrowIfCancellationRequested ( ) ;
return ( PropertyDeclarationSyntax ) context . Node ;
}
2025-08-19 01:39:09 +04:00
2026-03-06 20:01:54 +04:00
private static void ExecutePropertiesPipeline ( SourceProductionContext context , ImmutableArray < PropertyDeclarationSyntax > properties )
{
List < GeneratedMarkupPropertyModel > models = new List < GeneratedMarkupPropertyModel > ( ) ;
foreach ( PropertyDeclarationSyntax prop in properties )
2025-08-19 01:39:09 +04:00
{
2026-03-06 20:01:54 +04:00
context . CancellationToken . ThrowIfCancellationRequested ( ) ;
try
2025-08-19 01:39:09 +04:00
{
2026-03-06 20:01:54 +04:00
string returnType = prop . Type . ToString ( ) ;
bool anyErrors = false ;
Dictionary < string , MemberAccessExpressionSyntax > layout ;
if ( ! LayoutNames . TryGetValue ( returnType , out layout ! ) )
2025-08-19 01:39:09 +04:00
{
2026-03-06 20:01:54 +04:00
context . ReportDiagnostic ( Diagnostic . Create ( WrongReturnType , prop . Type . GetLocation ( ) ) ) ;
anyErrors = true ;
}
2025-08-19 01:39:09 +04:00
2026-03-06 20:01:54 +04:00
if ( ! prop . Modifiers . Any ( SyntaxKind . PartialKeyword ) )
{
context . ReportDiagnostic ( Diagnostic . Create ( NotPartialMethod , prop . Identifier . GetLocation ( ) ) ) ;
anyErrors = true ;
}
2025-08-19 01:39:09 +04:00
2026-03-06 20:01:54 +04:00
if ( prop . Initializer ! = null )
{
context . ReportDiagnostic ( Diagnostic . Create ( UseGetOnlyProperty , prop . Initializer . GetLocation ( ) ) ) ;
anyErrors = true ;
}
2025-08-19 01:39:09 +04:00
2026-03-06 20:01:54 +04:00
if ( prop . ExpressionBody ! = null )
{
context . ReportDiagnostic ( Diagnostic . Create ( UseGetOnlyProperty , prop . ExpressionBody . GetLocation ( ) ) ) ;
anyErrors = true ;
}
2025-08-19 01:39:09 +04:00
2026-03-06 20:01:54 +04:00
if ( prop . AccessorList ! = null )
{
foreach ( AccessorDeclarationSyntax accessor in prop . AccessorList . Accessors )
2025-08-19 01:39:09 +04:00
{
2026-03-06 20:01:54 +04:00
if ( accessor . IsKind ( SyntaxKind . SetAccessorDeclaration ) )
{
context . ReportDiagnostic ( Diagnostic . Create ( UseGetOnlyProperty , accessor . GetLocation ( ) ) ) ;
anyErrors = true ;
continue ;
}
2025-08-19 01:39:09 +04:00
2026-03-06 20:01:54 +04:00
if ( accessor . Body ! = null )
{
context . ReportDiagnostic ( Diagnostic . Create ( UseBodylessGetAccessor , accessor . Body . GetLocation ( ) ) ) ;
anyErrors = true ;
continue ;
}
if ( accessor . ExpressionBody ! = null )
2025-08-19 01:39:09 +04:00
{
2026-03-06 20:01:54 +04:00
context . ReportDiagnostic ( Diagnostic . Create ( UseBodylessGetAccessor , accessor . ExpressionBody . GetLocation ( ) ) ) ;
anyErrors = true ;
continue ;
2025-08-19 01:39:09 +04:00
}
}
2026-03-06 20:01:54 +04:00
}
2025-08-19 01:39:09 +04:00
2026-03-06 20:01:54 +04:00
if ( anyErrors | | layout = = null )
continue ;
2025-08-19 01:39:09 +04:00
2026-03-06 20:01:54 +04:00
SeparatedSyntaxList < CollectionElementSyntax > matrix = ParseAttributesMatrix ( context , layout , prop ) ;
PropertyDeclarationSyntax genProp = GeneratedPropertyDeclaration ( prop , SyntaxFactory . CollectionExpression ( matrix ) ) ;
models . Add ( new GeneratedMarkupPropertyModel ( prop , genProp ) ) ;
2025-08-19 01:39:09 +04:00
}
2026-03-06 20:01:54 +04:00
catch ( Exception ex )
{
context . AddSource ( $"{prop.Identifier}_Error.g.cs" , SourceText . From ( $"/* {ex} */" , Encoding . UTF8 ) ) ;
}
}
if ( models . Count = = 0 )
return ;
2025-08-19 01:39:09 +04:00
2026-03-06 20:01:54 +04:00
CompilationUnitSyntax compilationUnit = SyntaxFactory . CompilationUnit ( ) ;
SyntaxList < UsingDirectiveSyntax > usingDirectives = SyntaxFactory . List ( ParseUsings ( DefaultUsings ) ) ;
foreach ( GeneratedMarkupPropertyModel model in models )
{
2025-08-19 01:39:09 +04:00
context . CancellationToken . ThrowIfCancellationRequested ( ) ;
2026-03-06 20:01:54 +04:00
try
{
MemberDeclarationSyntax wrappedMember = WrapInParentDeclarations ( model . OriginalProperty , new List < MemberDeclarationSyntax > { model . GeneratedProperty } ) ;
compilationUnit = compilationUnit . AddMembers ( wrappedMember ) ;
}
catch ( Exception ex )
{
context . AddSource ( $"{model.OriginalProperty.Identifier}_GenError.g.cs" , SourceText . From ( $"/* {ex} */" , Encoding . UTF8 ) ) ;
}
}
2025-08-19 01:39:09 +04:00
2026-03-06 20:01:54 +04:00
compilationUnit = compilationUnit . WithUsings ( usingDirectives ) . NormalizeWhitespace ( ) ;
context . AddSource ( "GeneratedKeyboards.Properties.g.cs" , SourceText . From ( compilationUnit . ToFullString ( ) , Encoding . UTF8 ) ) ;
}
private static void ExecuteMethodsPipeline ( SourceProductionContext context , ImmutableArray < MethodDeclarationSyntax > methods )
{
List < GeneratedMarkupMethodModel > models = new List < GeneratedMarkupMethodModel > ( ) ;
foreach ( MethodDeclarationSyntax method in methods )
{
context . CancellationToken . ThrowIfCancellationRequested ( ) ;
try
2025-08-19 01:39:09 +04:00
{
2026-03-06 20:01:54 +04:00
string methodName = method . Identifier . Text ;
string returnType = method . ReturnType . ToString ( ) ;
bool anyErrors = false ;
2025-08-19 01:39:09 +04:00
2026-03-06 20:01:54 +04:00
Dictionary < string , MemberAccessExpressionSyntax > layout ;
if ( ! LayoutNames . TryGetValue ( returnType , out layout ! ) )
2025-08-19 01:39:09 +04:00
{
2026-03-06 20:01:54 +04:00
context . ReportDiagnostic ( Diagnostic . Create ( WrongReturnType , method . ReturnType . GetLocation ( ) ) ) ;
anyErrors = true ;
}
2025-08-19 01:39:09 +04:00
2026-03-06 20:01:54 +04:00
if ( ! method . Modifiers . Any ( SyntaxKind . PartialKeyword ) )
{
context . ReportDiagnostic ( Diagnostic . Create ( NotPartialMethod , method . Identifier . GetLocation ( ) ) ) ;
anyErrors = true ;
2025-08-19 01:39:09 +04:00
}
2026-03-06 20:01:54 +04:00
if ( method . ParameterList . Parameters . Count > 0 )
2025-08-19 01:39:09 +04:00
{
2026-03-06 20:01:54 +04:00
context . ReportDiagnostic ( Diagnostic . Create ( UseParametrlessMethod , method . ParameterList . GetLocation ( ) ) ) ;
anyErrors = true ;
2025-08-19 01:39:09 +04:00
}
2026-03-06 20:01:54 +04:00
if ( method . ExpressionBody ! = null )
{
context . ReportDiagnostic ( Diagnostic . Create ( UseBodylessMethod , method . ExpressionBody . GetLocation ( ) ) ) ;
anyErrors = true ;
}
2025-08-19 01:39:09 +04:00
2026-03-06 20:01:54 +04:00
if ( method . Body ! = null )
2025-08-16 13:13:34 +04:00
{
2026-03-06 20:01:54 +04:00
context . ReportDiagnostic ( Diagnostic . Create ( UseBodylessMethod , method . Body . GetLocation ( ) ) ) ;
anyErrors = true ;
}
2025-08-16 13:13:34 +04:00
2026-03-06 20:01:54 +04:00
if ( anyErrors | | layout = = null )
continue ;
2025-08-16 13:13:34 +04:00
2026-03-06 20:01:54 +04:00
SeparatedSyntaxList < CollectionElementSyntax > matrix = ParseAttributesMatrix ( context , layout , method ) ;
FieldDeclarationSyntax genField = GeneratedFieldDeclaration ( methodName , method . ReturnType , SyntaxFactory . CollectionExpression ( matrix ) ) ;
MethodDeclarationSyntax genMethod = GeneratedMethodDeclaration ( methodName , method . Modifiers , method . ReturnType , genField ) ;
models . Add ( new GeneratedMarkupMethodModel ( method , genField , genMethod ) ) ;
}
catch ( Exception ex )
{
context . AddSource ( $"{method.Identifier}_Error.g.cs" , SourceText . From ( $"/* {ex} */" , Encoding . UTF8 ) ) ;
}
}
2025-08-16 13:13:34 +04:00
2026-03-06 20:01:54 +04:00
if ( models . Count = = 0 )
return ;
2025-08-16 13:13:34 +04:00
2026-03-06 20:01:54 +04:00
CompilationUnitSyntax compilationUnit = SyntaxFactory . CompilationUnit ( ) ;
SyntaxList < UsingDirectiveSyntax > usingDirectives = SyntaxFactory . List ( ParseUsings ( DefaultUsings ) ) ;
2025-08-16 13:13:34 +04:00
2026-03-06 20:01:54 +04:00
foreach ( GeneratedMarkupMethodModel model in models )
{
context . CancellationToken . ThrowIfCancellationRequested ( ) ;
try
{
MemberDeclarationSyntax wrappedMembers = WrapInParentDeclarations ( model . OriginalMethod , new List < MemberDeclarationSyntax > { model . GeneratedField , model . GeneratedMethod } ) ;
compilationUnit = compilationUnit . AddMembers ( wrappedMembers ) ;
}
catch ( Exception ex )
{
context . AddSource ( $"{model.OriginalMethod.Identifier}_GenError.g.cs" , SourceText . From ( $"/* {ex} */" , Encoding . UTF8 ) ) ;
}
}
2025-08-16 13:13:34 +04:00
2026-03-06 20:01:54 +04:00
compilationUnit = compilationUnit . WithUsings ( usingDirectives ) . NormalizeWhitespace ( ) ;
context . AddSource ( "GeneratedKeyboards.Methods.g.cs" , SourceText . From ( compilationUnit . ToFullString ( ) , Encoding . UTF8 ) ) ;
}
2025-08-16 13:13:34 +04:00
2026-03-06 20:01:54 +04:00
private static SeparatedSyntaxList < CollectionElementSyntax > ParseAttributesMatrix ( SourceProductionContext context , Dictionary < string , MemberAccessExpressionSyntax > layout , MemberDeclarationSyntax member )
{
SeparatedSyntaxList < CollectionElementSyntax > vertical = new SeparatedSyntaxList < CollectionElementSyntax > ( ) ;
2025-08-16 13:13:34 +04:00
2026-03-06 20:01:54 +04:00
foreach ( AttributeListSyntax attributeList in member . AttributeLists )
{
2025-08-16 13:13:34 +04:00
context . CancellationToken . ThrowIfCancellationRequested ( ) ;
2026-03-06 20:01:54 +04:00
SeparatedSyntaxList < CollectionElementSyntax > horizontal = new SeparatedSyntaxList < CollectionElementSyntax > ( ) ;
2025-08-16 13:13:34 +04:00
2026-03-06 20:01:54 +04:00
foreach ( AttributeSyntax attribute in attributeList . Attributes )
2025-08-16 13:13:34 +04:00
{
context . CancellationToken . ThrowIfCancellationRequested ( ) ;
2026-03-06 20:01:54 +04:00
MemberAccessExpressionSyntax accessSyntax ;
if ( ! layout . TryGetValue ( attribute . Name . ToString ( ) , out accessSyntax ! ) )
2025-08-16 13:13:34 +04:00
{
2026-03-06 20:01:54 +04:00
context . ReportDiagnostic ( Diagnostic . Create ( UnsupportedAttribute , attribute . Name . GetLocation ( ) ) ) ;
continue ;
2025-08-16 13:13:34 +04:00
}
2026-03-06 20:01:54 +04:00
InvocationExpressionSyntax expression = SyntaxFactory . InvocationExpression ( accessSyntax , ConvertArguments ( attribute . ArgumentList ) ) ;
horizontal = horizontal . Add ( SyntaxFactory . ExpressionElement ( expression ) ) ;
2025-08-16 13:13:34 +04:00
}
2026-03-06 20:01:54 +04:00
ExpressionElementSyntax element = SyntaxFactory . ExpressionElement ( SyntaxFactory . CollectionExpression ( horizontal ) ) ;
vertical = vertical . Add ( element ) ;
2025-08-19 01:39:09 +04:00
}
2026-03-06 20:01:54 +04:00
return vertical ;
}
2025-08-19 01:39:09 +04:00
2026-03-06 20:01:54 +04:00
private static PropertyDeclarationSyntax GeneratedPropertyDeclaration ( PropertyDeclarationSyntax property , CollectionExpressionSyntax collection )
{
return SyntaxFactory . PropertyDeclaration ( property . Type , property . Identifier )
. WithExpressionBody ( SyntaxFactory . ArrowExpressionClause ( collection ) )
. WithSemicolonToken ( SyntaxFactory . Token ( SyntaxKind . SemicolonToken ) ) ;
}
2025-08-19 01:39:09 +04:00
2026-03-06 20:01:54 +04:00
private static MethodDeclarationSyntax GeneratedMethodDeclaration ( string identifier , SyntaxTokenList modifiers , TypeSyntax returnType , FieldDeclarationSyntax field )
{
VariableDeclaratorSyntax targetVariable = field . Declaration . Variables . First ( ) ;
2025-08-19 01:39:09 +04:00
2026-03-06 20:01:54 +04:00
return SyntaxFactory . MethodDeclaration ( returnType , identifier )
. WithModifiers ( modifiers )
. WithExpressionBody ( SyntaxFactory . ArrowExpressionClause ( SyntaxFactory . IdentifierName ( targetVariable . Identifier ) ) )
. WithSemicolonToken ( SyntaxFactory . Token ( SyntaxKind . SemicolonToken ) ) ;
}
2025-08-19 01:39:09 +04:00
2026-03-06 20:01:54 +04:00
private static FieldDeclarationSyntax GeneratedFieldDeclaration ( string identifier , TypeSyntax returnType , CollectionExpressionSyntax collection )
{
ArgumentSyntax argument = SyntaxFactory . Argument ( collection ) ;
ArgumentListSyntax arguments = SyntaxFactory . ArgumentList ( SyntaxFactory . SingletonSeparatedList ( argument ) ) ;
ObjectCreationExpressionSyntax objectCreation = SyntaxFactory . ObjectCreationExpression ( returnType , arguments , null ) ;
2025-08-19 01:39:09 +04:00
2026-03-06 20:01:54 +04:00
VariableDeclaratorSyntax declarator = SyntaxFactory . VariableDeclarator ( identifier + "_generatedMarkup" )
. WithInitializer ( SyntaxFactory . EqualsValueClause ( objectCreation ) ) ;
2025-08-16 13:13:34 +04:00
2026-03-06 20:01:54 +04:00
SyntaxTokenList fieldModifiers = SyntaxFactory . TokenList (
SyntaxFactory . Token ( SyntaxKind . PrivateKeyword ) ,
SyntaxFactory . Token ( SyntaxKind . StaticKeyword ) ,
SyntaxFactory . Token ( SyntaxKind . ReadOnlyKeyword ) ) ;
2025-08-16 13:13:34 +04:00
2026-03-06 20:01:54 +04:00
return SyntaxFactory . FieldDeclaration ( SyntaxFactory . VariableDeclaration ( returnType ) . AddVariables ( declarator ) )
. WithModifiers ( fieldModifiers ) ;
}
2025-08-16 13:13:34 +04:00
2026-03-06 20:01:54 +04:00
private static ArgumentListSyntax ConvertArguments ( AttributeArgumentListSyntax ? attributeArgs )
{
if ( attributeArgs = = null )
return SyntaxFactory . ArgumentList ( ) ;
2025-08-16 13:13:34 +04:00
2026-03-06 20:01:54 +04:00
IEnumerable < ArgumentSyntax > arguments = attributeArgs . Arguments . Select ( CastArgument ) ;
return SyntaxFactory . ArgumentList ( SyntaxFactory . SeparatedList ( arguments ) ) ;
}
2025-08-16 13:13:34 +04:00
2026-03-06 20:01:54 +04:00
private static ArgumentSyntax CastArgument ( AttributeArgumentSyntax argument )
{
if ( argument . NameColon ! = null )
2025-08-16 13:13:34 +04:00
{
2026-03-06 20:01:54 +04:00
return SyntaxFactory . Argument ( argument . Expression ) . WithNameColon ( argument . NameColon ) ;
}
return SyntaxFactory . Argument ( argument . Expression ) ;
}
private static MemberDeclarationSyntax WrapInParentDeclarations ( MemberDeclarationSyntax originalMember , List < MemberDeclarationSyntax > generatedMembers )
{
SyntaxNode ? parentNode = originalMember . Parent ;
2025-08-16 13:13:34 +04:00
2026-03-06 20:01:54 +04:00
if ( parentNode is not ClassDeclarationSyntax )
{
throw new InvalidOperationException ( "Generated member must be contained within a class." ) ;
2025-08-16 13:13:34 +04:00
}
2026-03-06 20:01:54 +04:00
MemberDeclarationSyntax currentDeclaration = SyntaxFactory . ClassDeclaration ( ( ( ClassDeclarationSyntax ) parentNode ) . Identifier )
. WithMembers ( SyntaxFactory . List ( generatedMembers ) )
. WithModifiers ( ( ( ClassDeclarationSyntax ) parentNode ) . Modifiers ) ;
if ( ! currentDeclaration . Modifiers . Any ( SyntaxKind . PartialKeyword ) )
2025-08-16 13:13:34 +04:00
{
2026-03-06 20:01:54 +04:00
currentDeclaration = ( ( ClassDeclarationSyntax ) currentDeclaration ) . AddModifiers ( SyntaxFactory . Token ( SyntaxKind . PartialKeyword ) ) ;
}
2025-08-16 13:13:34 +04:00
2026-03-06 20:01:54 +04:00
parentNode = parentNode . Parent ;
2025-08-16 13:13:34 +04:00
2026-03-06 20:01:54 +04:00
while ( parentNode is TypeDeclarationSyntax typeDeclaration )
{
ClassDeclarationSyntax wrappingClass = SyntaxFactory . ClassDeclaration ( typeDeclaration . Identifier )
. WithMembers ( SyntaxFactory . SingletonList ( currentDeclaration ) )
. WithModifiers ( typeDeclaration . Modifiers ) ;
2025-08-16 13:13:34 +04:00
2026-03-06 20:01:54 +04:00
if ( ! wrappingClass . Modifiers . Any ( SyntaxKind . PartialKeyword ) )
2025-08-16 13:13:34 +04:00
{
2026-03-06 20:01:54 +04:00
wrappingClass = wrappingClass . AddModifiers ( SyntaxFactory . Token ( SyntaxKind . PartialKeyword ) ) ;
}
2025-08-16 13:13:34 +04:00
2026-03-06 20:01:54 +04:00
currentDeclaration = wrappingClass ;
parentNode = parentNode . Parent ;
}
2025-08-16 13:13:34 +04:00
2026-03-06 20:01:54 +04:00
if ( parentNode is BaseNamespaceDeclarationSyntax namespaceDeclaration )
{
currentDeclaration = SyntaxFactory . NamespaceDeclaration ( namespaceDeclaration . Name )
. WithMembers ( SyntaxFactory . SingletonList ( currentDeclaration ) ) ;
}
2025-08-16 13:13:34 +04:00
2026-03-06 20:01:54 +04:00
return currentDeclaration ;
}
2025-08-16 13:13:34 +04:00
2026-03-06 20:01:54 +04:00
private static IEnumerable < UsingDirectiveSyntax > ParseUsings ( params string [ ] names )
{
return names . Select ( name = > SyntaxFactory . UsingDirective ( SyntaxFactory . ParseName ( name ) ) ) ;
}
2025-08-16 13:13:34 +04:00
2026-03-06 20:01:54 +04:00
private static bool HasGenAttributes ( MemberDeclarationSyntax member )
{
IEnumerable < string > memberAttributes = member . AttributeLists
. SelectMany ( x = > x . Attributes )
. Select ( x = > x . Name . ToString ( ) ) ;
2025-08-16 13:13:34 +04:00
2026-03-06 20:01:54 +04:00
IEnumerable < string > targetAttributes = InlineAttributes . Concat ( ReplyAttributes ) ;
2025-08-16 13:13:34 +04:00
2026-03-06 20:01:54 +04:00
return memberAttributes . Intersect ( targetAttributes ) . Any ( ) ;
}
2025-08-16 13:13:34 +04:00
2026-03-06 20:01:54 +04:00
private static MemberAccessExpressionSyntax AccessExpression ( string className , string methodName )
{
return SyntaxFactory . MemberAccessExpression (
SyntaxKind . SimpleMemberAccessExpression ,
SyntaxFactory . IdentifierName ( className ) ,
SyntaxFactory . IdentifierName ( methodName ) ) ;
}
2025-08-16 13:13:34 +04:00
2026-03-06 20:01:54 +04:00
private class GeneratedMarkupMethodModel
{
public MethodDeclarationSyntax OriginalMethod { get ; }
public FieldDeclarationSyntax GeneratedField { get ; }
public MethodDeclarationSyntax GeneratedMethod { get ; }
2025-08-16 13:13:34 +04:00
2026-03-06 20:01:54 +04:00
public GeneratedMarkupMethodModel ( MethodDeclarationSyntax originalMethod , FieldDeclarationSyntax generatedField , MethodDeclarationSyntax generatedMethod )
{
OriginalMethod = originalMethod ;
GeneratedField = generatedField ;
GeneratedMethod = generatedMethod ;
}
}
2025-08-16 13:13:34 +04:00
2026-03-06 20:01:54 +04:00
private class GeneratedMarkupPropertyModel
{
public PropertyDeclarationSyntax OriginalProperty { get ; }
public PropertyDeclarationSyntax GeneratedProperty { get ; }
2025-08-16 13:13:34 +04:00
2026-03-06 20:01:54 +04:00
public GeneratedMarkupPropertyModel ( PropertyDeclarationSyntax originalProperty , PropertyDeclarationSyntax generatedProperty )
{
OriginalProperty = originalProperty ;
GeneratedProperty = generatedProperty ;
}
2025-08-16 13:13:34 +04:00
}
2026-03-06 20:01:54 +04:00
}