2025-07-24 23:19:59 +04:00
using Microsoft.CodeAnalysis ;
using Microsoft.CodeAnalysis.CSharp.Syntax ;
using System.Collections.Immutable ;
using System.Text ;
using System.Xml.Linq ;
2025-08-16 13:13:34 +04:00
using Telegrator.RoslynExtensions ;
2025-07-24 23:19:59 +04:00
2025-08-18 21:15:34 +04:00
#if DEBUG
2025-08-16 13:13:34 +04:00
namespace Telegrator.RoslynGenerators
2025-07-24 23:19:59 +04:00
{
/// <summary>
/// Source Generator для автоматической генерации Markdown-документации по публичному API Telegrator.
/// </summary>
[Generator]
public class ApiMarkdownGenerator : IIncrementalGenerator
{
public void Initialize ( IncrementalGeneratorInitializationContext context )
{
IncrementalValueProvider < ImmutableArray < BaseTypeDeclarationSyntax > > typeDeclarations = context . SyntaxProvider
. CreateSyntaxProvider (
predicate : ( node , _ ) = > node is ClassDeclarationSyntax | | node is InterfaceDeclarationSyntax | | node is StructDeclarationSyntax | | node is EnumDeclarationSyntax ,
transform : ( ctx , _ ) = > ( BaseTypeDeclarationSyntax ) ctx . Node
)
. Where ( typeDecl = > typeDecl ! = null )
. Collect ( ) ;
var combined = typeDeclarations . Combine ( context . CompilationProvider ) ;
context . RegisterSourceOutput ( combined , ( spc , source ) = >
{
IReadOnlyList < BaseTypeDeclarationSyntax > typeDecls = source . Left ;
Compilation compilation = source . Right ;
string markdown = GenerateMarkdown ( typeDecls , compilation ) ;
spc . AddSource ( "TelegramReactive_Api.md" , markdown ) ;
} ) ;
}
private string GenerateMarkdown ( IReadOnlyList < BaseTypeDeclarationSyntax > typeDecls , Compilation compilation )
{
StringBuilder sourceBuilder = new StringBuilder ( "/*\n" ) ;
// Writing caution message
sourceBuilder . AppendLine ( "> [!CAUTION]" ) ;
sourceBuilder . AppendLine ( "> This page was generated using hand-writen compiler's XML-Summaries output parser" ) ;
sourceBuilder . AppendLine ( "> If you find any missing syntax, errors in structure, mis-formats or empty sections, please contact owner or open an issue\n" ) ;
// Collecting only public types
IEnumerable < INamedTypeSymbol > publicTypes = typeDecls
. Select ( type = > type . TryGetNamedType ( compilation ) )
. Where ( symbol = > symbol ! = null )
. Where ( symbol = > symbol . DeclaredAccessibility = = Accessibility . Public ) ;
// Grouping by namespace
IOrderedEnumerable < IGrouping < string , INamedTypeSymbol > > namespaces = publicTypes
. GroupBy ( t = > t . ContainingNamespace . ToDisplayString ( ) )
. OrderBy ( g = > g . Key ) ;
foreach ( IGrouping < string , INamedTypeSymbol > nsGroup in namespaces )
{
string ns = nsGroup . Key = = "Telegrator" ? nsGroup . Key : nsGroup . Key . Substring ( "Telegrator." . Length ) ;
sourceBuilder . AppendFormat ( "# {0}\n\n" , ns ) ;
foreach ( INamedTypeSymbol type in nsGroup . OrderBy ( t = > t . Name ) )
{
// Формируем generic-параметры для заголовка класса
string genericArgs = type . FormatGenericTypes ( ) ;
sourceBuilder . AppendFormat ( "## {0} `{1}{2}`\n\n" , type . TypeKind , type . Name , genericArgs ) ;
string? typeSummary = type . ExtractSummary ( ) ;
if ( ! string . IsNullOrWhiteSpace ( typeSummary ) )
sourceBuilder . AppendFormat ( "> {0}\n\n" , typeSummary ) ;
2025-07-26 21:03:42 +04:00
// Writing fields
2025-07-24 23:19:59 +04:00
if ( type . TypeKind = = TypeKind . Enum )
{
WriteEnumValues ( sourceBuilder , type ) ;
}
else
{
WriteConstructors ( sourceBuilder , type ) ;
WriteProperties ( sourceBuilder , type ) ;
WriteMethods ( sourceBuilder , type ) ;
}
}
}
return sourceBuilder . AppendLine ( ) . AppendLine ( "*/" ) . ToString ( ) ;
}
private static void WriteConstructors ( StringBuilder sourceBuilder , INamedTypeSymbol type )
{
if ( type . TypeKind = = TypeKind . Enum )
return ;
// Getting ctors
List < IMethodSymbol > ctors = type . Constructors
. Where ( c = > c . DeclaredAccessibility = = Accessibility . Public )
. ToList ( ) ;
// Checking for any
if ( ctors . Count = = 0 )
return ;
// Writing
sourceBuilder . AppendLine ( "**Constructors:**" ) ;
foreach ( IMethodSymbol ctor in ctors )
{
2025-07-26 21:03:42 +04:00
// Formatting constructor signature
2025-07-24 23:19:59 +04:00
string genericArgs = type . FormatGenericTypes ( ) ;
string parameters = string . Join ( ", " , ctor . Parameters . Select ( p = > p . Type . GetShortName ( ) ) ) ;
string signature = string . Format ( "{0}{1}({2})" , type . Name , genericArgs , parameters ) ;
sourceBuilder . Append ( " - `" ) . Append ( signature ) . Append ( "`" ) . AppendLine ( ) ;
// Writing summary
string? propSummary = ctor . ExtractSummary ( ) ;
if ( ! string . IsNullOrWhiteSpace ( propSummary ) )
2025-07-26 21:03:42 +04:00
sourceBuilder . Append ( " > " ) . Append ( propSummary ) ;
sourceBuilder . AppendLine ( ) ;
2025-07-24 23:19:59 +04:00
}
sourceBuilder . AppendLine ( ) ;
}
private static void WriteEnumValues ( StringBuilder sourceBuilder , INamedTypeSymbol type )
{
2025-07-26 21:03:42 +04:00
// Getting enum values
List < IFieldSymbol > fields = type
. GetMembers ( )
. OfType < IFieldSymbol > ( )
. Where ( f = > f . HasConstantValue & & f . DeclaredAccessibility = = Accessibility . Public )
. ToList ( ) ;
// Checking for any
if ( fields . Count = = 0 )
2025-07-24 23:19:59 +04:00
return ;
2025-07-26 21:03:42 +04:00
// Writing
2025-07-24 23:19:59 +04:00
sourceBuilder . AppendLine ( "**Values:**" ) ;
2025-07-26 21:03:42 +04:00
foreach ( IFieldSymbol field in fields )
2025-07-24 23:19:59 +04:00
{
2025-07-26 21:03:42 +04:00
// Writing value
2025-07-24 23:19:59 +04:00
sourceBuilder . Append ( "- `" ) . Append ( field . Name ) . Append ( "`" ) ;
2025-07-26 21:03:42 +04:00
// Writing summary
2025-07-24 23:19:59 +04:00
string? summary = field . ExtractSummary ( ) ;
if ( ! string . IsNullOrWhiteSpace ( summary ) )
sourceBuilder . Append ( " — " ) . Append ( summary ) ;
2025-07-26 21:03:42 +04:00
2025-07-24 23:19:59 +04:00
sourceBuilder . AppendLine ( ) ;
}
2025-07-26 21:03:42 +04:00
2025-07-24 23:19:59 +04:00
sourceBuilder . AppendLine ( ) ;
}
private static void WriteProperties ( StringBuilder sourceBuilder , INamedTypeSymbol type )
{
// Getting properties
List < IPropertySymbol > props = type
. GetMembers ( )
. OfType < IPropertySymbol > ( )
. Where ( p = > p . DeclaredAccessibility = = Accessibility . Public )
. ToList ( ) ;
// Checking for any
if ( props . Count = = 0 )
return ;
// Writing
sourceBuilder . AppendLine ( "**Properties:**" ) ;
foreach ( IPropertySymbol prop in props )
{
// Writing member
sourceBuilder . AppendFormat ( "- `{0}`\n" , prop . Name ) ;
// Writing summary
string? propSummary = prop . ExtractSummary ( ) ;
if ( ! string . IsNullOrWhiteSpace ( propSummary ) )
sourceBuilder . AppendFormat ( " > {0}" , propSummary ) ;
sourceBuilder . AppendLine ( ) ;
}
sourceBuilder . AppendLine ( ) ;
}
private static void WriteMethods ( StringBuilder sourceBuilder , INamedTypeSymbol type )
{
// Getting methods
List < IMethodSymbol > methods = type
. GetMembers ( )
. OfType < IMethodSymbol > ( )
. Where ( m = > m . DeclaredAccessibility = = Accessibility . Public )
. Where ( m = > m . MethodKind = = MethodKind . Ordinary )
. ToList ( ) ;
// Checking for any
if ( methods . Count = = 0 )
return ;
// Writing
sourceBuilder . AppendLine ( "**Methods:**" ) ;
foreach ( IMethodSymbol method in methods )
{
2025-07-26 21:03:42 +04:00
// Formating method signature
2025-07-24 23:19:59 +04:00
string genericArgs = method . FormatGenericTypes ( ) ;
2025-07-26 21:03:42 +04:00
string parameters = string . Join ( ", " , method . Parameters . Select ( p = > p . Type . GetShortName ( ) ) . Where ( p = > ! string . IsNullOrEmpty ( p ) ) ) ;
2025-07-24 23:19:59 +04:00
sourceBuilder . AppendFormat ( " - `{0}{1}({2})`\n" , method . Name , genericArgs , parameters ) ;
// Writing summary
string? propSummary = method . ExtractSummary ( ) ;
if ( ! string . IsNullOrWhiteSpace ( propSummary ) )
sourceBuilder . AppendFormat ( " > {0}" , propSummary ) ;
sourceBuilder . AppendLine ( ) ;
}
sourceBuilder . AppendLine ( ) ;
}
}
internal static partial class TypesExtensions
{
public static string? ExtractSummary ( this ISymbol symbol )
{
string? xmlDoc = symbol . GetDocumentationCommentXml ( ) ;
if ( string . IsNullOrWhiteSpace ( xmlDoc ) )
return null ;
try
{
XDocument doc = XDocument . Parse ( xmlDoc ) ;
2025-07-26 21:03:42 +04:00
XElement ? xSummary = doc . Root ? . Element ( "summary" ) ;
2025-07-24 23:19:59 +04:00
2025-07-26 21:03:42 +04:00
if ( xSummary = = null )
2025-07-24 23:19:59 +04:00
return null ;
// Убираем лишние пробелы и переносы строк
2025-07-26 21:03:42 +04:00
string summary = xSummary . Value . Trim ( ) . Replace ( "\n" , " " ) ;
while ( summary . Contains ( " " ) )
summary = summary . Replace ( " " , " " ) ;
return summary ;
2025-07-24 23:19:59 +04:00
}
catch
{
return null ;
}
}
public static string FormatGenericTypes ( this INamedTypeSymbol methodSymbol )
{
if ( methodSymbol . TypeParameters . Length = = 0 )
return string . Empty ;
string typeParams = string . Join ( ", " , methodSymbol . TypeParameters . Select ( tp = > tp . Name ) ) ;
return string . Format ( "<{0}>" , typeParams ) ;
}
public static string FormatGenericTypes ( this IMethodSymbol methodSymbol )
{
if ( methodSymbol . TypeParameters . Length = = 0 )
return string . Empty ;
string typeParams = string . Join ( ", " , methodSymbol . TypeParameters . Select ( tp = > tp . Name ) ) ;
return string . Format ( "<{0}>" , typeParams ) ;
}
public static string GetShortName ( this ITypeSymbol type )
{
if ( type is INamedTypeSymbol namedType & & namedType . IsGenericType )
{
string genericArgs = string . Join ( ", " , namedType . TypeArguments . Select ( GetShortName ) ) ;
return string . Format ( "{0}<{1}>" , namedType . Name , genericArgs ) ;
}
if ( type . TypeKind = = TypeKind . TypeParameter )
{
return type . Name ;
}
return type . Name ;
}
}
2025-08-18 21:15:34 +04:00
}
#endif