* Added ReplyKeyboardMarkupGenerator
* Added set of markup attributes dedicatedf to map a keyboard using partial methods
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
namespace Telegrator.RoslynExtensions
|
||||
{
|
||||
public static class CollectionsExtensions
|
||||
{
|
||||
public static IEnumerable<TSource> Combine<TSource>(params IEnumerable<TSource>[] collections)
|
||||
=> collections.SelectMany(x => x);
|
||||
|
||||
public static IEnumerable<TSource> IntersectBy<TSource, TValue>(this IEnumerable<TSource> first, IEnumerable<TValue> second, Func<TSource, TValue> selector)
|
||||
{
|
||||
foreach (TSource item in first)
|
||||
{
|
||||
TValue value = selector(item);
|
||||
if (second.Contains(value))
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
|
||||
public static IList<TValue> UnionAdd<TValue>(this IList<TValue> source, IEnumerable<TValue> toUnion)
|
||||
{
|
||||
foreach (TValue toUnionValue in toUnion)
|
||||
{
|
||||
if (!source.Contains(toUnionValue, EqualityComparer<TValue>.Default))
|
||||
source.Add(toUnionValue);
|
||||
}
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
public static void UnionAdd<TSource>(this ICollection<TSource> collection, IEnumerable<TSource> target)
|
||||
{
|
||||
foreach (TSource item in target)
|
||||
{
|
||||
if (!collection.Contains(item))
|
||||
collection.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
public static void UnionAdd<TSource>(this SortedList<TSource, TSource> collection, IEnumerable<TSource> target)
|
||||
{
|
||||
foreach (TSource item in target)
|
||||
{
|
||||
if (!collection.Values.Contains(item))
|
||||
collection.Add(item, item);
|
||||
}
|
||||
}
|
||||
|
||||
public static int IndexOf<T>(this IEnumerable<T> source, Func<T, bool> predicate)
|
||||
{
|
||||
int index = 0;
|
||||
foreach (T item in source)
|
||||
{
|
||||
if (predicate.Invoke(item))
|
||||
return index;
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static IEnumerable<T> Repeat<T>(this T item, int times)
|
||||
=> Enumerable.Range(0, times - 1).Select(_ => item);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace Telegrator.RoslynExtensions
|
||||
{
|
||||
public static class DiagnosticsHelper
|
||||
{
|
||||
public static Diagnostic Create(this DiagnosticDescriptor descriptor, Location? location, params object[] messageArgs)
|
||||
=> Diagnostic.Create(descriptor, location, messageArgs);
|
||||
|
||||
public static void Report(this Diagnostic diagnostic, SourceProductionContext context)
|
||||
=> context.ReportDiagnostic(diagnostic);
|
||||
|
||||
public static void Report(this DiagnosticDescriptor descriptor, SourceProductionContext context, Location? location, params object[] messageArgs)
|
||||
=> descriptor.Create(location, messageArgs).Report(context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Telegrator.RoslynExtensions;
|
||||
|
||||
public class TargteterNotFoundException() : Exception() { }
|
||||
|
||||
public class BaseClassTypeNotFoundException() : Exception() { }
|
||||
|
||||
public class AncestorNotFoundException : Exception { }
|
||||
@@ -0,0 +1,8 @@
|
||||
// This file is used by Code Analysis to maintain SuppressMessage
|
||||
// attributes that are applied to this project.
|
||||
// Project-level suppressions either have no target or are given
|
||||
// a specific target and scoped to a namespace, type, member, etc.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
[assembly: SuppressMessage("Style", "IDE0090")]
|
||||
@@ -0,0 +1,31 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace Telegrator.RoslynExtensions
|
||||
{
|
||||
public static class MemberDeclarationSyntaxExtensions
|
||||
{
|
||||
private static SyntaxTrivia TabulationTrivia => SyntaxFactory.SyntaxTrivia(SyntaxKind.WhitespaceTrivia, "\t");
|
||||
private static SyntaxTrivia WhitespaceTrivia => SyntaxFactory.SyntaxTrivia(SyntaxKind.WhitespaceTrivia, " ");
|
||||
private static SyntaxTrivia NewLineTrivia => SyntaxFactory.SyntaxTrivia(SyntaxKind.EndOfLineTrivia, "\n");
|
||||
private static SyntaxToken Semicolon => SyntaxFactory.Token(SyntaxKind.SemicolonToken);
|
||||
|
||||
public static SyntaxTokenList Decorate(this SyntaxTokenList tokens)
|
||||
=> new SyntaxTokenList(tokens.Select(token => token.WithoutTrivia().WithTrailingTrivia(WhitespaceTrivia)).ToArray());
|
||||
|
||||
public static T DecorateMember<T>(this T typeDeclaration, int times = 1) where T : MemberDeclarationSyntax => typeDeclaration
|
||||
.WithoutTrivia().WithLeadingTrivia(TabulationTrivia.Repeat(times)).WithTrailingTrivia(NewLineTrivia);
|
||||
|
||||
public static NamespaceDeclarationSyntax Decorate(this NamespaceDeclarationSyntax namespaceDeclaration) => namespaceDeclaration
|
||||
.WithName(namespaceDeclaration.Name.WithoutTrivia().WithLeadingTrivia(WhitespaceTrivia))
|
||||
.WithOpenBraceToken(SyntaxFactory.Token(SyntaxKind.OpenBraceToken).WithLeadingTrivia(NewLineTrivia).WithTrailingTrivia(NewLineTrivia))
|
||||
.WithCloseBraceToken(SyntaxFactory.Token(SyntaxKind.CloseBraceToken));
|
||||
|
||||
public static T Decorate<T>(this T typeDeclaration, int times = 1) where T : TypeDeclarationSyntax => (T)typeDeclaration
|
||||
.WithoutTrivia().WithLeadingTrivia(TabulationTrivia.Repeat(times))
|
||||
.WithIdentifier(typeDeclaration.Identifier.WithoutTrivia().WithLeadingTrivia(WhitespaceTrivia).WithTrailingTrivia(NewLineTrivia))
|
||||
.WithOpenBraceToken(SyntaxFactory.Token(SyntaxKind.OpenBraceToken).WithLeadingTrivia(TabulationTrivia.Repeat(times)).WithTrailingTrivia(NewLineTrivia))
|
||||
.WithCloseBraceToken(SyntaxFactory.Token(SyntaxKind.CloseBraceToken).WithLeadingTrivia(TabulationTrivia.Repeat(times)).WithTrailingTrivia(NewLineTrivia));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Text;
|
||||
|
||||
namespace Telegrator.RoslynExtensions
|
||||
{
|
||||
public static class StringBuilderExtensions
|
||||
{
|
||||
public static StringBuilder AppendTabs(this StringBuilder builder, int count)
|
||||
=> builder.Append(new string('\t', count));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace Telegrator.RoslynExtensions
|
||||
{
|
||||
public static class StringExtensions
|
||||
{
|
||||
public static string FirstLetterToUpper(this string target)
|
||||
{
|
||||
char[] chars = target.ToCharArray();
|
||||
int index = chars.IndexOf(char.IsLetter);
|
||||
chars[index] = char.ToUpper(chars[index]);
|
||||
return new string(chars);
|
||||
}
|
||||
|
||||
public static string FirstLetterToLower(this string target)
|
||||
{
|
||||
char[] chars = target.ToCharArray();
|
||||
int index = chars.IndexOf(char.IsLetter);
|
||||
chars[index] = char.ToLower(chars[index]);
|
||||
return new string(chars);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace Telegrator.RoslynExtensions;
|
||||
|
||||
public static class SymbolsExtensions
|
||||
{
|
||||
public static bool IsAssignableFrom(this ITypeSymbol symbol, string className)
|
||||
{
|
||||
if (symbol.BaseType == null)
|
||||
return false;
|
||||
|
||||
if (symbol.BaseType.Name == className)
|
||||
return true;
|
||||
|
||||
return symbol.BaseType.IsAssignableFrom(className);
|
||||
}
|
||||
|
||||
public static ITypeSymbol? Cast(this ITypeSymbol symbol, string className)
|
||||
{
|
||||
if (symbol.BaseType == null)
|
||||
return null;
|
||||
|
||||
if (symbol.BaseType.Name == className)
|
||||
return symbol.BaseType;
|
||||
|
||||
return symbol.BaseType.Cast(className);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace Telegrator.RoslynExtensions
|
||||
{
|
||||
public static class SyntaxNodesExtensions
|
||||
{
|
||||
public static T FindAncestor<T>(this SyntaxNode node) where T : SyntaxNode
|
||||
{
|
||||
if (node.Parent == null)
|
||||
throw new AncestorNotFoundException();
|
||||
|
||||
if (node.Parent is T found)
|
||||
return found;
|
||||
|
||||
return node.Parent.FindAncestor<T>();
|
||||
}
|
||||
|
||||
public static bool TryFindAncestor<T>(this SyntaxNode node, out T syntax) where T : SyntaxNode
|
||||
{
|
||||
if (node.Parent == null)
|
||||
{
|
||||
syntax = null!;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (node.Parent is T found)
|
||||
{
|
||||
syntax = found;
|
||||
return true;
|
||||
}
|
||||
|
||||
return node.Parent.TryFindAncestor(out syntax);
|
||||
}
|
||||
|
||||
public static INamedTypeSymbol TryGetNamedType(this BaseTypeDeclarationSyntax syntax, Compilation compilation)
|
||||
{
|
||||
SemanticModel semanticModel = compilation.GetSemanticModel(syntax.SyntaxTree);
|
||||
return semanticModel.GetDeclaredSymbol(syntax)!;
|
||||
}
|
||||
|
||||
public static string GetBaseTypeSyntaxName(this BaseTypeSyntax baseClassSyntax)
|
||||
{
|
||||
if (baseClassSyntax is PrimaryConstructorBaseTypeSyntax parimaryConstructor)
|
||||
return parimaryConstructor.Type.ToString();
|
||||
|
||||
if (baseClassSyntax is SimpleBaseTypeSyntax simpleBaseType)
|
||||
return simpleBaseType.Type.ToString();
|
||||
|
||||
throw new BaseClassTypeNotFoundException();
|
||||
}
|
||||
|
||||
public static int CountParentTree(this SyntaxNode node)
|
||||
{
|
||||
int count = 0;
|
||||
SyntaxNode inspectNode = node;
|
||||
|
||||
while (inspectNode.Parent != null)
|
||||
{
|
||||
inspectNode = inspectNode.Parent;
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
public static SeparatedSyntaxList<TNode> ToSeparatedSyntaxList<TNode>(this IEnumerable<TNode> elements) where TNode : SyntaxNode
|
||||
=> new SeparatedSyntaxList<TNode>().AddRange(elements);
|
||||
|
||||
public static SyntaxList<TNode> ToSyntaxList<TNode>(this IEnumerable<TNode> elements) where TNode : SyntaxNode
|
||||
=> new SyntaxList<TNode>().AddRange(elements);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace Telegrator.RoslynExtensions
|
||||
{
|
||||
public static class SyntaxTokenExtensions
|
||||
{
|
||||
public static bool HasModifiers(this SyntaxTokenList modifiers, params string[] expected)
|
||||
{
|
||||
return modifiers.Count(mod => expected.Contains(mod.ToString())) == expected.Length;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
|
||||
<IncludeBuildOutput>False</IncludeBuildOutput>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="4.14.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Common" Version="4.14.0" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
|
||||
|
||||
<PackageReference Include="PolySharp" Version="1.15.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,288 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using System.Collections.Immutable;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
using Telegrator.RoslynExtensions;
|
||||
|
||||
namespace Telegrator.RoslynGenerators
|
||||
{
|
||||
/// <summary>
|
||||
/// Source Generator для автоматической генерации Markdown-документации по публичному API Telegrator.
|
||||
/// </summary>
|
||||
[Generator]
|
||||
public class ApiMarkdownGenerator : IIncrementalGenerator
|
||||
{
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
#if RELEASE
|
||||
// DEBUG ONLY GENERATOR
|
||||
return;
|
||||
#endif
|
||||
|
||||
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);
|
||||
|
||||
// Writing fields
|
||||
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)
|
||||
{
|
||||
// Formatting constructor signature
|
||||
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))
|
||||
sourceBuilder.Append(" > ").Append(propSummary);
|
||||
|
||||
sourceBuilder.AppendLine();
|
||||
}
|
||||
|
||||
sourceBuilder.AppendLine();
|
||||
}
|
||||
|
||||
private static void WriteEnumValues(StringBuilder sourceBuilder, INamedTypeSymbol type)
|
||||
{
|
||||
// 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)
|
||||
return;
|
||||
|
||||
// Writing
|
||||
sourceBuilder.AppendLine("**Values:**");
|
||||
foreach (IFieldSymbol field in fields)
|
||||
{
|
||||
// Writing value
|
||||
sourceBuilder.Append("- `").Append(field.Name).Append("`");
|
||||
|
||||
// Writing summary
|
||||
string? summary = field.ExtractSummary();
|
||||
if (!string.IsNullOrWhiteSpace(summary))
|
||||
sourceBuilder.Append(" — ").Append(summary);
|
||||
|
||||
sourceBuilder.AppendLine();
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
// Formating method signature
|
||||
string genericArgs = method.FormatGenericTypes();
|
||||
string parameters = string.Join(", ", method.Parameters.Select(p => p.Type.GetShortName()).Where(p => !string.IsNullOrEmpty(p)));
|
||||
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);
|
||||
XElement? xSummary = doc.Root?.Element("summary");
|
||||
|
||||
if (xSummary == null)
|
||||
return null;
|
||||
|
||||
// Убираем лишние пробелы и переносы строк
|
||||
string summary = xSummary.Value.Trim().Replace("\n", " ");
|
||||
while (summary.Contains(" "))
|
||||
summary = summary.Replace(" ", " ");
|
||||
|
||||
return summary;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// This file is used by Code Analysis to maintain SuppressMessage
|
||||
// attributes that are applied to this project.
|
||||
// Project-level suppressions either have no target or are given
|
||||
// a specific target and scoped to a namespace, type, member, etc.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
[assembly: SuppressMessage("Style", "IDE0090")]
|
||||
@@ -0,0 +1,209 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using System.Collections.Immutable;
|
||||
using System.Text;
|
||||
using Telegrator.RoslynExtensions;
|
||||
|
||||
namespace Telegrator.RoslynGenerators
|
||||
{
|
||||
[Generator(LanguageNames.CSharp)]
|
||||
public class ImplicitHandlerBuilderExtensionsGenerator : IIncrementalGenerator
|
||||
{
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
IncrementalValueProvider<ImmutableArray<ClassDeclarationSyntax>> pipeline = context.SyntaxProvider
|
||||
.CreateSyntaxProvider(SyntaxPredicate, SyntaxTransform)
|
||||
.Where(declaration => declaration != null)
|
||||
.Collect();
|
||||
|
||||
context.RegisterImplementationSourceOutput(pipeline, GenerateSource);
|
||||
}
|
||||
|
||||
private static bool SyntaxPredicate(SyntaxNode node, CancellationToken _)
|
||||
{
|
||||
if (node is not ClassDeclarationSyntax)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static ClassDeclarationSyntax SyntaxTransform(GeneratorSyntaxContext context, CancellationToken _)
|
||||
{
|
||||
ISymbol? symbol = context.SemanticModel.GetDeclaredSymbol(context.Node);
|
||||
if (symbol is null)
|
||||
return null!;
|
||||
|
||||
if (symbol is not ITypeSymbol typeSymbol)
|
||||
return null!;
|
||||
|
||||
if (!typeSymbol.IsAssignableFrom("UpdateFilterAttribute"))
|
||||
return null!;
|
||||
|
||||
return (ClassDeclarationSyntax)context.Node;
|
||||
}
|
||||
|
||||
private static void GenerateSource(SourceProductionContext context, ImmutableArray<ClassDeclarationSyntax> declarations)
|
||||
{
|
||||
StringBuilder source = new StringBuilder();
|
||||
Dictionary<string, string> targeters = [];
|
||||
List<string> usingDirectives =
|
||||
[
|
||||
"using Telegrator.Handlers.Building;",
|
||||
"using Telegrator.Handlers.Building.Components;"
|
||||
];
|
||||
|
||||
StringBuilder sourceBuilder = new StringBuilder()
|
||||
.AppendLine("namespace Telegrator")
|
||||
.AppendLine("{")
|
||||
.Append("\t//").Append(string.Join(", ", declarations.Select(decl => decl.Identifier.ToString()))).AppendLine()
|
||||
.AppendLine("\tpublic static partial class HandlerBuilderExtensions")
|
||||
.AppendLine("\t{");
|
||||
|
||||
List<ClassDeclarationSyntax> lateTargeterClasses = [];
|
||||
foreach (ClassDeclarationSyntax classDeclaration in declarations)
|
||||
{
|
||||
try
|
||||
{
|
||||
usingDirectives.UnionAdd(classDeclaration.FindAncestor<CompilationUnitSyntax>().Usings.Select(use => use.ToString()));
|
||||
ParseClassDeclaration(sourceBuilder, classDeclaration, targeters);
|
||||
}
|
||||
catch (TargteterNotFoundException)
|
||||
{
|
||||
lateTargeterClasses.Add(classDeclaration);
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
string errorFormat = string.Format("\t\t// failed to generate for {0} : {1}", classDeclaration.Identifier.ToString(), exc.GetType().Name);
|
||||
sourceBuilder.AppendLine(errorFormat);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (ClassDeclarationSyntax classDeclaration in lateTargeterClasses)
|
||||
{
|
||||
try
|
||||
{
|
||||
usingDirectives.UnionAdd(classDeclaration.FindAncestor<CompilationUnitSyntax>().Usings.Select(use => use.ToString()));
|
||||
ParseClassDeclaration(sourceBuilder, classDeclaration, targeters);
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
string errorFormat = string.Format("\t\t// failed to generate for {0} : {1}", classDeclaration.Identifier.ToString(), exc.GetType().Name);
|
||||
sourceBuilder.AppendLine(errorFormat);
|
||||
}
|
||||
}
|
||||
|
||||
sourceBuilder.AppendLine("\t}\n}");
|
||||
sourceBuilder.Insert(0, string.Join("\n", usingDirectives.Select(use => use.ToString()).OrderBy(use => use)) + "\n\n");
|
||||
context.AddSource("GeneratedHandlerBuilderExtensions.cs", sourceBuilder.ToString());
|
||||
}
|
||||
|
||||
private static void ParseClassDeclaration(StringBuilder sourceBuilder, ClassDeclarationSyntax classDeclaration, Dictionary<string, string> targeters)
|
||||
{
|
||||
string className = classDeclaration.Identifier.ToString();
|
||||
if (className == "FilterAnnotation")
|
||||
return;
|
||||
|
||||
IEnumerable<MethodDeclarationSyntax> methods = classDeclaration.Members.OfType<MethodDeclarationSyntax>();
|
||||
MethodDeclarationSyntax? targeterMethod = methods.FirstOrDefault(method => method.Identifier.ToString() == "GetFilterringTarget");
|
||||
|
||||
string filterName = className.Replace("Attribute", string.Empty);
|
||||
string classTargetterMethodName = filterName + "_GetFilterringTarget";
|
||||
|
||||
if (targeterMethod != null)
|
||||
{
|
||||
targeters.Add(className, classTargetterMethodName);
|
||||
RenderTargeterMethod(sourceBuilder, classTargetterMethodName, targeterMethod);
|
||||
sourceBuilder.AppendLine();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (classDeclaration.BaseList == null)
|
||||
throw new Exception();
|
||||
|
||||
string baseClassName = classDeclaration.BaseList.Types
|
||||
.ElementAt(0).GetBaseTypeSyntaxName();
|
||||
|
||||
if (!targeters.ContainsKey(baseClassName))
|
||||
throw new TargteterNotFoundException();
|
||||
|
||||
classTargetterMethodName = targeters[baseClassName];
|
||||
}
|
||||
|
||||
if (classDeclaration.Modifiers.Any(keyword => keyword.ValueText == "abstract"))
|
||||
return;
|
||||
|
||||
if (classDeclaration.ParameterList != null)
|
||||
{
|
||||
if (classDeclaration.BaseList != null)
|
||||
{
|
||||
PrimaryConstructorBaseTypeSyntax primaryConstructor = (PrimaryConstructorBaseTypeSyntax)classDeclaration.BaseList.Types.ElementAt(0);
|
||||
RenderExtensionMethod(sourceBuilder, filterName, classTargetterMethodName, classDeclaration.ParameterList.Parameters, primaryConstructor.ArgumentList.Arguments);
|
||||
}
|
||||
else
|
||||
{
|
||||
RenderExtensionMethod(sourceBuilder, filterName, classTargetterMethodName, classDeclaration.ParameterList.Parameters, []);
|
||||
}
|
||||
|
||||
sourceBuilder.AppendLine();
|
||||
}
|
||||
|
||||
foreach (ConstructorDeclarationSyntax constructor in classDeclaration.Members.OfType<ConstructorDeclarationSyntax>())
|
||||
{
|
||||
if (constructor.Initializer == null)
|
||||
continue;
|
||||
|
||||
RenderExtensionMethod(sourceBuilder, filterName, classTargetterMethodName, constructor.ParameterList.Parameters, constructor.Initializer.ArgumentList.Arguments);
|
||||
sourceBuilder.AppendLine();
|
||||
}
|
||||
}
|
||||
|
||||
private static void RenderExtensionMethod(StringBuilder sourceBuilder, string filterName, string classTargetterMethodName, SeparatedSyntaxList<ParameterSyntax> parameters, SeparatedSyntaxList<ArgumentSyntax> arguments)
|
||||
{
|
||||
if (filterName == "ChatType")
|
||||
filterName = "InChatType"; // Because it conflicting
|
||||
|
||||
sourceBuilder
|
||||
.Append("\t\t/// <summary>").AppendLine()
|
||||
.Append("\t\t/// Adds ").Append(filterName).Append(" filter to implicit handler").AppendLine()
|
||||
.Append("\t\t/// </summary>").AppendLine();
|
||||
|
||||
sourceBuilder.Append("\t\tpublic static TBuilder ").Append(filterName).Append("<TBuilder>(this TBuilder builder");
|
||||
|
||||
if (parameters.Any())
|
||||
sourceBuilder.Append(", ").Append(parameters.ToFullString());
|
||||
|
||||
sourceBuilder
|
||||
.Append(") where TBuilder : IHandlerBuilder").AppendLine()
|
||||
.Append("\t\t{").AppendLine()
|
||||
.Append("\t\t\tbuilder.AddTargetedFilter");
|
||||
|
||||
if (arguments.Count > 1)
|
||||
sourceBuilder.Append("s");
|
||||
|
||||
sourceBuilder.Append("(").Append(classTargetterMethodName);
|
||||
|
||||
if (arguments.Any())
|
||||
sourceBuilder.Append(", ").Append(arguments.ToFullString());
|
||||
|
||||
sourceBuilder
|
||||
.Append(");").AppendLine()
|
||||
.Append("\t\t\treturn builder;").AppendLine()
|
||||
.Append("\t\t}").AppendLine();
|
||||
}
|
||||
|
||||
private static void RenderTargeterMethod(StringBuilder sourceBuilder, string classTargetterMethodName, MethodDeclarationSyntax targeterMethod)
|
||||
{
|
||||
sourceBuilder.Append("\t\tprivate static ").Append(targeterMethod.ReturnType.ToString()).Append(" ").Append(classTargetterMethodName).Append(targeterMethod.ParameterList.ToFullString());
|
||||
|
||||
if (targeterMethod.ExpressionBody != null)
|
||||
{
|
||||
sourceBuilder.Append(targeterMethod.ExpressionBody.ToFullString()).Append(";").AppendLine();
|
||||
}
|
||||
else if (targeterMethod.Body != null)
|
||||
{
|
||||
sourceBuilder.Append(targeterMethod.Body.ToFullString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
|
||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||
<IsRoslynComponent>True</IsRoslynComponent>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="4.14.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Common" Version="4.14.0" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Telegrator.RoslynExtensions\Telegrator.RoslynExtensions.csproj" PrivateAssets="all" />
|
||||
<None Include="$(OutputPath)\*.dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user