From cf598ea91e4dad6581e13c1d2f8d746d0d666ad5 Mon Sep 17 00:00:00 2001 From: Rikitav Date: Sat, 16 Aug 2025 13:13:34 +0400 Subject: [PATCH] * Added ReplyKeyboardMarkupGenerator * Added set of markup attributes dedicatedf to map a keyboard using partial methods --- .../AnalyzerReleases.Unshipped.md | 5 + .../DeveloperHelperAnalyzer.cs | 5 +- Telegrator.Analyzers/DiagnosticsHelper.cs | 14 - Telegrator.Analyzers/Exceptions.cs | 12 - .../GeneratedKeyboardMarkupGenerator.cs | 341 ++++++++++++++++++ .../Telegrator.Analyzers.csproj | 34 +- Telegrator.Analyzers/TypeExtensions.cs | 87 ----- Telegrator.Generators/Exceptions.cs | 12 - .../Telegrator.Generators.csproj | 22 -- Telegrator.Generators/TypeExtensions.cs | 67 ---- .../Telegrator.Hosting.Web.csproj | 24 +- Telegrator.Hosting/Telegrator.Hosting.csproj | 2 +- Telegrator.Tests/Telegrator.Tests.csproj | 1 - Telegrator.sln | 24 +- ...GeneratedInlineKeyboardMarkupAttributes.cs | 76 ++++ ...GeneratedReplyKeybooardMarkupAttributes.cs | 6 + Telegrator/Telegrator.csproj | 41 +-- .../CollectionsExtensions.cs | 64 ++++ .../DiagnosticsHelper.cs | 16 + dev/Telegrator.RoslynExtensions/Exceptions.cs | 7 + .../GlobalSuppressions.cs | 0 .../MemberDeclarationSyntaxExtensions.cs | 31 ++ .../StringBuilderExtensions.cs | 10 + .../StringExtensions.cs | 21 ++ .../SymbolsExtensions.cs | 28 ++ .../SyntaxNodesExtensions.cs | 74 ++++ .../SyntaxTokenExtensions.cs | 12 + .../Telegrator.RoslynExtensions.csproj | 26 ++ .../ApiMarkdownGenerator.cs | 4 +- .../GlobalSuppressions.cs | 8 + ...plicitHandlerBuilderExtensionsGenerator.cs | 11 +- .../Telegrator.RoslynGenerators.csproj | 28 ++ 32 files changed, 829 insertions(+), 284 deletions(-) delete mode 100644 Telegrator.Analyzers/DiagnosticsHelper.cs delete mode 100644 Telegrator.Analyzers/Exceptions.cs create mode 100644 Telegrator.Analyzers/GeneratedKeyboardMarkupGenerator.cs delete mode 100644 Telegrator.Analyzers/TypeExtensions.cs delete mode 100644 Telegrator.Generators/Exceptions.cs delete mode 100644 Telegrator.Generators/Telegrator.Generators.csproj delete mode 100644 Telegrator.Generators/TypeExtensions.cs create mode 100644 Telegrator/Markups/GeneratedInlineKeyboardMarkupAttributes.cs create mode 100644 Telegrator/Markups/GeneratedReplyKeybooardMarkupAttributes.cs create mode 100644 dev/Telegrator.RoslynExtensions/CollectionsExtensions.cs create mode 100644 dev/Telegrator.RoslynExtensions/DiagnosticsHelper.cs create mode 100644 dev/Telegrator.RoslynExtensions/Exceptions.cs rename {Telegrator.Generators => dev/Telegrator.RoslynExtensions}/GlobalSuppressions.cs (100%) create mode 100644 dev/Telegrator.RoslynExtensions/MemberDeclarationSyntaxExtensions.cs create mode 100644 dev/Telegrator.RoslynExtensions/StringBuilderExtensions.cs create mode 100644 dev/Telegrator.RoslynExtensions/StringExtensions.cs create mode 100644 dev/Telegrator.RoslynExtensions/SymbolsExtensions.cs create mode 100644 dev/Telegrator.RoslynExtensions/SyntaxNodesExtensions.cs create mode 100644 dev/Telegrator.RoslynExtensions/SyntaxTokenExtensions.cs create mode 100644 dev/Telegrator.RoslynExtensions/Telegrator.RoslynExtensions.csproj rename {Telegrator.Generators => dev/Telegrator.RoslynGenerators}/ApiMarkdownGenerator.cs (99%) create mode 100644 dev/Telegrator.RoslynGenerators/GlobalSuppressions.cs rename {Telegrator.Generators => dev/Telegrator.RoslynGenerators}/ImplicitHandlerBuilderExtensionsGenerator.cs (97%) create mode 100644 dev/Telegrator.RoslynGenerators/Telegrator.RoslynGenerators.csproj diff --git a/Telegrator.Analyzers/AnalyzerReleases.Unshipped.md b/Telegrator.Analyzers/AnalyzerReleases.Unshipped.md index eb6ae75..363bfe5 100644 --- a/Telegrator.Analyzers/AnalyzerReleases.Unshipped.md +++ b/Telegrator.Analyzers/AnalyzerReleases.Unshipped.md @@ -5,4 +5,9 @@ Rule ID | Category | Severity | Notes --------|----------|----------|------- +TG_1001 | Modelling | Error | GeneratedKeyboardMarkupGenerator +TG_1002 | Modelling | Error | GeneratedKeyboardMarkupGenerator +TG_1003 | Modelling | Error | GeneratedKeyboardMarkupGenerator +TG_1004 | Modelling | Error | GeneratedKeyboardMarkupGenerator +TG_1005 | Modelling | Error | GeneratedKeyboardMarkupGenerator TR0001 | Aspect | Error | DiagnosticsHelper \ No newline at end of file diff --git a/Telegrator.Analyzers/DeveloperHelperAnalyzer.cs b/Telegrator.Analyzers/DeveloperHelperAnalyzer.cs index 4985dc0..7fd8271 100644 --- a/Telegrator.Analyzers/DeveloperHelperAnalyzer.cs +++ b/Telegrator.Analyzers/DeveloperHelperAnalyzer.cs @@ -2,6 +2,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using System.Collections.Immutable; using System.Text; +using Telegrator.RoslynExtensions; namespace Telegrator.Analyzers { @@ -33,7 +34,7 @@ namespace Telegrator.Analyzers private static HandlerDeclarationModel Transform(GeneratorSyntaxContext context, CancellationToken cancellationToken) { ClassDeclarationSyntax classSyntax = (ClassDeclarationSyntax)context.Node; - IEnumerable attributes = classSyntax.GetHandlerAttributes(); + IEnumerable attributes = []; //classSyntax.GetHandlerAttributes(); BaseTypeSyntax? baseType = classSyntax.GetHandlerBaseClass(); if (baseType == null && !attributes.Any()) @@ -58,7 +59,7 @@ namespace Telegrator.Analyzers context.CancellationToken.ThrowIfCancellationRequested(); try { - usingDirectives.UnionAdd(handler.ClassDeclaration.FindCompilationUnitSyntax().Usings.Select(use => use.ToString())); + usingDirectives.UnionAdd(handler.ClassDeclaration.FindAncestor().Usings.Select(use => use.ToString())); ParseHandlerDeclaration(context, sourceBuilder, handler, context.CancellationToken); } catch (Exception ex) when (ex is not OperationCanceledException) diff --git a/Telegrator.Analyzers/DiagnosticsHelper.cs b/Telegrator.Analyzers/DiagnosticsHelper.cs deleted file mode 100644 index 2e41103..0000000 --- a/Telegrator.Analyzers/DiagnosticsHelper.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Microsoft.CodeAnalysis; - -namespace Telegrator.Analyzers -{ - public static class DiagnosticsHelper - { - public const string Aspect = "Aspect"; - - public static readonly DiagnosticDescriptor Test = new DiagnosticDescriptor("TR0001", "Test descriptor", string.Empty, Aspect, DiagnosticSeverity.Error, true, "Test diagnostic description."); - - public static Diagnostic Create(this DiagnosticDescriptor descriptor, Location? location, params object[] messageArgs) - => Diagnostic.Create(descriptor, location, messageArgs); - } -} diff --git a/Telegrator.Analyzers/Exceptions.cs b/Telegrator.Analyzers/Exceptions.cs deleted file mode 100644 index 10d6301..0000000 --- a/Telegrator.Analyzers/Exceptions.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Telegrator.Analyzers -{ - /// - /// Exception thrown when a target is not found during code generation. - /// - internal class TargteterNotFoundException() : Exception() { } - - /// - /// Exception thrown when a base class type is not found during code generation. - /// - internal class BaseClassTypeNotFoundException() : Exception() { } -} diff --git a/Telegrator.Analyzers/GeneratedKeyboardMarkupGenerator.cs b/Telegrator.Analyzers/GeneratedKeyboardMarkupGenerator.cs new file mode 100644 index 0000000..0ab2479 --- /dev/null +++ b/Telegrator.Analyzers/GeneratedKeyboardMarkupGenerator.cs @@ -0,0 +1,341 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Collections.Immutable; +using Telegrator.RoslynExtensions; + +#if DEBUG +using System.Diagnostics; +#endif + +namespace Telegrator.Analyzers +{ + [Generator(LanguageNames.CSharp)] + public class GeneratedKeyboardMarkupGenerator : IIncrementalGenerator + { + private const string InlineReturnType = "InlineKeyboardMarkup"; + private const string ReplyReturnType = "ReplyKeyboardMarkup"; + + 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 WebAppAttribute = "WebApp"; + + private static readonly string[] InlineAttributes = [CallbackDataAttribute, CallbackGameAttribute, CopyTextAttribute, LoginRequestAttribute, PayRequestAttribute, UrlRedirectAttribute, WebAppAttribute, SwitchQueryAttribute, QueryChosenAttribute, QueryCurrentAttribute]; + private static readonly string[] ReplyAttributes = []; + private static readonly string[] DefaultUsings = ["Telegram.Bot.Types.ReplyMarkups"]; + + private static readonly Dictionary InlineKeyboardLayout = new Dictionary() + { + { 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 ReplyKeyboardLayout = new Dictionary() + { + + }; + + private static readonly Dictionary> LayoutNames = new Dictionary>() + { + { InlineReturnType, InlineKeyboardLayout }, + { ReplyReturnType, ReplyKeyboardLayout } + }; + + private static readonly DiagnosticDescriptor WrongReturnType = new DiagnosticDescriptor("TG_1001", "Wrong return type", string.Empty, "Modelling", DiagnosticSeverity.Error, true); + private static readonly DiagnosticDescriptor UnsupportedAttribute = new DiagnosticDescriptor("TG_1002", "Unsupported attribute", string.Empty, "Modelling", DiagnosticSeverity.Error, true); + private static readonly DiagnosticDescriptor NotPartialMethod = new DiagnosticDescriptor("TG_1003", "Not a partial method", string.Empty, "Modelling", DiagnosticSeverity.Error, true); + private static readonly DiagnosticDescriptor UseBodylessMethod = new DiagnosticDescriptor("TG_1004", "Use bodyless method", string.Empty, "Modelling", DiagnosticSeverity.Error, true); + private static readonly DiagnosticDescriptor UseParametrlessMethod = new DiagnosticDescriptor("TG_1005", "Use parametrless method", string.Empty, "Modelling", DiagnosticSeverity.Error, true); + + 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 void Initialize(IncrementalGeneratorInitializationContext context) + { + IncrementalValueProvider> pipeline = context.SyntaxProvider.CreateSyntaxProvider(Provide, Transform).Where(x => x != null).Collect(); + context.RegisterSourceOutput(pipeline, Execute); + } + + private static bool Provide(SyntaxNode syntaxNode, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + if (syntaxNode is not MethodDeclarationSyntax method) + return false; + + if (!HasGenAttributes(method)) + return false; + + return true; + } + + private static MethodDeclarationSyntax Transform(GeneratorSyntaxContext context, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + return (MethodDeclarationSyntax)context.Node; + } + + private static void Execute(SourceProductionContext context, ImmutableArray methods) + { + List models = []; + foreach (MethodDeclarationSyntax method in methods) + { + context.CancellationToken.ThrowIfCancellationRequested(); + try + { + string methodName = method.Identifier.Text; + string returnType = method.ReturnType.ToString(); + bool anyErrors = false; + + if (!LayoutNames.TryGetValue(returnType, out var layout)) + { + WrongReturnType.Report(context, method.ReturnType.GetLocation()); + anyErrors = true; + } + + if (!method.Modifiers.HasModifiers("partial")) + { + NotPartialMethod.Report(context, method.Identifier.GetLocation()); + anyErrors = true; + } + + if (method.ParameterList.Parameters.Any()) + { + UseParametrlessMethod.Report(context, method.ParameterList.GetLocation()); + anyErrors = true; + } + + if (method.ExpressionBody != null) + { + UseBodylessMethod.Report(context, method.ExpressionBody.GetLocation()); + anyErrors = true; + } + + if (method.Body != null) + { + UseBodylessMethod.Report(context, method.Body.GetLocation()); + anyErrors = true; + } + + if (anyErrors) + return; + + context.CancellationToken.ThrowIfCancellationRequested(); + SeparatedSyntaxList vertical = new SeparatedSyntaxList(); + + foreach (AttributeListSyntax attributeList in method.AttributeLists) + { + context.CancellationToken.ThrowIfCancellationRequested(); + SeparatedSyntaxList horizontal = new SeparatedSyntaxList(); + + foreach (AttributeSyntax attribute in attributeList.Attributes) + { + context.CancellationToken.ThrowIfCancellationRequested(); + + if (!layout.TryGetValue(attribute.Name.ToString(), out var accessSyntax)) + { + UnsupportedAttribute.Report(context, attribute.Name.GetLocation()); + return; + } + + InvocationExpressionSyntax expression = SyntaxFactory.InvocationExpression(accessSyntax, ConvertArguments(attribute.ArgumentList)); + horizontal = horizontal.Add(SyntaxFactory.ExpressionElement(expression)); + } + + ExpressionElementSyntax element = SyntaxFactory.ExpressionElement(SyntaxFactory.CollectionExpression(horizontal)); + vertical = vertical.Add(element); + } + + FieldDeclarationSyntax genField = GeneratedFieldDeclaration(methodName, method.ReturnType.WithoutTrivia(), SyntaxFactory.CollectionExpression(vertical)); + MethodDeclarationSyntax genMethod = GeneratedMethodDeclaration(methodName, method.Modifiers, method.ReturnType, genField); + + models.Add(new GeneratedMarkupMethodModel(method, genField, genMethod)); + } + catch (Exception ex) + { + context.AddSource(method.Identifier.ToString(), ex.ToString()); + } + } + + context.CancellationToken.ThrowIfCancellationRequested(); + CompilationUnitSyntax compilationUnit = SyntaxFactory.CompilationUnit(); + SyntaxList usingDirectives = ParseUsings(DefaultUsings).ToSyntaxList(); + + foreach (GeneratedMarkupMethodModel model in models) + { + context.CancellationToken.ThrowIfCancellationRequested(); + + try + { + if (model.OriginalMethod.Parent is not ClassDeclarationSyntax containerClass) + throw new MissingMemberException(); + + FieldDeclarationSyntax genField = model.GeneratedField + .WithLeadingTrivia(NewLineTrivia, TabulationTrivia, TabulationTrivia); + + MethodDeclarationSyntax genMethod = model.GeneratedMethod + .WithLeadingTrivia(NewLineTrivia, TabulationTrivia, TabulationTrivia); + + //ClassDeclarationSyntax genClass = GeneratedClassDeclaration(containerClass.Identifier.WithLeadingTrivia(WhitespaceTrivia).WithTrailingTrivia(NewLineTrivia), containerClass.Modifiers, genField, genMethod); + NamespaceDeclarationSyntax genNamespace = GeneratedNamespaceDeclaration(model.OriginalMethod, [genField, genMethod]); + + genNamespace = genNamespace + .WithCloseBraceToken(genNamespace.CloseBraceToken.WithLeadingTrivia(NewLineTrivia)); + + compilationUnit = compilationUnit.AddMembers(genNamespace); + } + catch (Exception ex) + { + context.AddSource(model.OriginalMethod.Identifier.ToString(), ex.ToString()); + } + } + + compilationUnit = compilationUnit.WithUsings(usingDirectives); + context.AddSource("GeneratedKeyboards.g", compilationUnit.ToFullString()); + } + + /* + private static NamespaceDeclarationSyntax GeneratedNamespaceDeclaration(NameSyntax name, params IEnumerable members) + { + NamespaceDeclarationSyntax genNamespace = SyntaxFactory.NamespaceDeclaration(name) + .WithMembers(new SyntaxList(members)) + .WithLeadingTrivia(NewLineTrivia); + + return genNamespace + .WithCloseBraceToken(genNamespace.CloseBraceToken.WithLeadingTrivia(NewLineTrivia)); + } + + private static ClassDeclarationSyntax GeneratedClassDeclaration(SyntaxToken identifier, SyntaxTokenList modifiers, params IEnumerable members) + { + ClassDeclarationSyntax genClass = SyntaxFactory.ClassDeclaration(identifier) + .WithMembers(new SyntaxList(members)) + .WithModifiers(modifiers) + .WithLeadingTrivia(NewLineTrivia, TabulationTrivia); + + return genClass + .WithOpenBraceToken(genClass.OpenBraceToken.WithLeadingTrivia(TabulationTrivia)) + .WithCloseBraceToken(genClass.CloseBraceToken.WithLeadingTrivia(NewLineTrivia, TabulationTrivia)); + } + */ + + private static MethodDeclarationSyntax GeneratedMethodDeclaration(string identifier, SyntaxTokenList modifiers, TypeSyntax returnType, FieldDeclarationSyntax field) + { + return SyntaxFactory.MethodDeclaration(returnType.WithTrailingTrivia(WhitespaceTrivia), identifier) + .WithModifiers(modifiers) + .WithExpressionBody(SyntaxFactory.ArrowExpressionClause(SyntaxFactory.IdentifierName(field.Declaration.Variables.ElementAt(0).Identifier))) + .WithSemicolonToken(Semicolon); + } + + private static FieldDeclarationSyntax GeneratedFieldDeclaration(string identifier, TypeSyntax returnType, CollectionExpressionSyntax collection) + { + ArgumentListSyntax arguments = SyntaxFactory.ArgumentList(SeparatedSyntaxList(SyntaxFactory.Argument(collection))); + ObjectCreationExpressionSyntax objectCreation = SyntaxFactory.ObjectCreationExpression(returnType.WithLeadingTrivia(WhitespaceTrivia), arguments, null); + + VariableDeclaratorSyntax declarator = SyntaxFactory.VariableDeclarator(identifier + "_generatedMarkup") + .WithInitializer(SyntaxFactory.EqualsValueClause(objectCreation)); + + return SyntaxFactory.FieldDeclaration(SyntaxFactory.VariableDeclaration(returnType.WithTrailingTrivia(WhitespaceTrivia)).AddVariables(declarator)) + .WithModifiers(Modifiers(SyntaxKind.PrivateKeyword, SyntaxKind.StaticKeyword, SyntaxKind.ReadOnlyKeyword)); + } + + private static ArgumentListSyntax ConvertArguments(AttributeArgumentListSyntax? attributeArgs) + { + if (attributeArgs == null) + return SyntaxFactory.ArgumentList(); + + return SyntaxFactory.ArgumentList(SeparatedSyntaxList(attributeArgs.Arguments.Select(CastArgument))); + } + + private static NamespaceDeclarationSyntax GeneratedNamespaceDeclaration(MethodDeclarationSyntax method, IEnumerable generatedMembers) + { + if (method.Parent is not ClassDeclarationSyntax containerClass) + throw new MemberAccessException(); + + int times = method.CountParentTree() - 1; + ClassDeclarationSyntax generatedContainerClass = SyntaxFactory.ClassDeclaration(containerClass.Identifier) + .WithMembers(new SyntaxList(generatedMembers.Select(member => member.DecorateMember(times + 1)))) + .WithModifiers(containerClass.Modifiers.Decorate()) + .Decorate(times); + + MemberDeclarationSyntax generated = generatedContainerClass; + MemberDeclarationSyntax inspecting = containerClass; + + while (inspecting.Parent != null) + { + times -= 1; + if (inspecting.Parent is not MemberDeclarationSyntax inspectingMember) + break; + + inspecting = inspectingMember; + switch (inspectingMember) + { + case ClassDeclarationSyntax classDeclaration: + { + generated = SyntaxFactory.ClassDeclaration(classDeclaration.Identifier) + .WithMembers([generated]) + .WithModifiers(classDeclaration.Modifiers.Decorate()) + .Decorate(times); + + break; + } + + case StructDeclarationSyntax structDeclaration: + { + generated = SyntaxFactory.StructDeclaration(structDeclaration.Identifier) + .WithMembers([generated]) + .WithModifiers(structDeclaration.Modifiers.Decorate()) + .Decorate(times); + + break; + } + + case NamespaceDeclarationSyntax namespaceDeclaration: + { + //foundNamespaceDeclaration = namespaceDeclaration; + return SyntaxFactory.NamespaceDeclaration(namespaceDeclaration.Name) + .WithMembers([generated]).Decorate(); + } + } + } + + throw new AncestorNotFoundException(); + } + + private static ArgumentSyntax CastArgument(AttributeArgumentSyntax argument) + => SyntaxFactory.Argument(argument.Expression).WithNameColon(argument.NameColon); + + private static SyntaxTokenList Modifiers(params SyntaxKind[] kinds) + => new SyntaxTokenList(kinds.Select(SyntaxFactory.Token).Select(mod => mod.WithTrailingTrivia(WhitespaceTrivia))); + + private static IEnumerable ParseUsings(params string[] names) => names + .Select(name => SyntaxFactory.IdentifierName(name).WithLeadingTrivia(WhitespaceTrivia)) + .Select(name => SyntaxFactory.UsingDirective(name).WithTrailingTrivia(NewLineTrivia)); + + private static bool HasGenAttributes(MethodDeclarationSyntax method) => method.AttributeLists.SelectMany(x => x.Attributes) + .Select(x => x.Name.ToString()).Intersect(InlineAttributes.Concat(ReplyAttributes)).Any(); + + private static SeparatedSyntaxList SeparatedSyntaxList(params IEnumerable elements) where T : SyntaxNode + => new SeparatedSyntaxList().AddRange(elements); + + private static MemberAccessExpressionSyntax AccessExpression(string className, string methodName) + => SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, SyntaxFactory.IdentifierName(className), SyntaxFactory.IdentifierName(methodName)); + + private record class GeneratedMarkupMethodModel(MethodDeclarationSyntax OriginalMethod, FieldDeclarationSyntax GeneratedField, MethodDeclarationSyntax GeneratedMethod); + } +} diff --git a/Telegrator.Analyzers/Telegrator.Analyzers.csproj b/Telegrator.Analyzers/Telegrator.Analyzers.csproj index 4192c34..d00dae0 100644 --- a/Telegrator.Analyzers/Telegrator.Analyzers.csproj +++ b/Telegrator.Analyzers/Telegrator.Analyzers.csproj @@ -1,21 +1,31 @@  - netstandard2.0 - latest - enable - enable - true - Debug;Release;AnalyzersDebug + netstandard2.0 + latest + enable + enable + true + true - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + diff --git a/Telegrator.Analyzers/TypeExtensions.cs b/Telegrator.Analyzers/TypeExtensions.cs deleted file mode 100644 index 7d1241a..0000000 --- a/Telegrator.Analyzers/TypeExtensions.cs +++ /dev/null @@ -1,87 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using System.Collections; -using System.Text; - -namespace Telegrator.Analyzers -{ - internal static class TypeExtensions - { - public static StringBuilder AppendTabs(this StringBuilder builder, int count) - => builder.Append(new string('\t', count)); - - 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 bool IsAssignableFrom(this ITypeSymbol? symbol, string className) - { - if (symbol is null) - return false; - - 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); - } - - public static IEnumerable WhereCast(this IEnumerable source) - { - foreach (object value in source) - { - if (value is TResult result) - yield return result; - } - } - - public static CompilationUnitSyntax FindCompilationUnitSyntax(this SyntaxNode syntax) - { - while (syntax is not CompilationUnitSyntax) - syntax = syntax.Parent ?? throw new Exception(); - - return (CompilationUnitSyntax)syntax; - } - - public static IEnumerable IntersectBy(this IEnumerable first, IEnumerable second, Func selector) - { - foreach (TSource item in first) - { - TValue value = selector(item); - if (second.Contains(value)) - yield return item; - } - } - - public static IList UnionAdd(this IList source, IEnumerable toUnion) - { - foreach (TValue toUnionValue in toUnion) - { - if (!source.Contains(toUnionValue, EqualityComparer.Default)) - source.Add(toUnionValue); - } - - return source; - } - } -} diff --git a/Telegrator.Generators/Exceptions.cs b/Telegrator.Generators/Exceptions.cs deleted file mode 100644 index 896c59b..0000000 --- a/Telegrator.Generators/Exceptions.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Telegrator.Generators -{ - /// - /// Exception thrown when a target is not found during code generation. - /// - internal class TargteterNotFoundException() : Exception() { } - - /// - /// Exception thrown when a base class type is not found during code generation. - /// - internal class BaseClassTypeNotFoundException() : Exception() { } -} diff --git a/Telegrator.Generators/Telegrator.Generators.csproj b/Telegrator.Generators/Telegrator.Generators.csproj deleted file mode 100644 index 440b258..0000000 --- a/Telegrator.Generators/Telegrator.Generators.csproj +++ /dev/null @@ -1,22 +0,0 @@ - - - - netstandard2.0 - latest - enable - enable - true - Debug;Release;AnalyzersDebug - True - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - diff --git a/Telegrator.Generators/TypeExtensions.cs b/Telegrator.Generators/TypeExtensions.cs deleted file mode 100644 index a6aa23d..0000000 --- a/Telegrator.Generators/TypeExtensions.cs +++ /dev/null @@ -1,67 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; - -namespace Telegrator.Generators -{ - internal static partial class TypeExtensions - { - 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 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); - } - - public static CompilationUnitSyntax FindCompilationUnitSyntax(this SyntaxNode syntax) - { - while (syntax is not CompilationUnitSyntax) - syntax = syntax.Parent ?? throw new Exception(); - - return (CompilationUnitSyntax)syntax; - } - - public static IList UnionAdd(this IList source, IEnumerable toUnion) - { - foreach (TValue toUnionValue in toUnion) - { - if (!source.Contains(toUnionValue, EqualityComparer.Default)) - source.Add(toUnionValue); - } - - return source; - } - } -} diff --git a/Telegrator.Hosting.Web/Telegrator.Hosting.Web.csproj b/Telegrator.Hosting.Web/Telegrator.Hosting.Web.csproj index 18ad4a5..628d940 100644 --- a/Telegrator.Hosting.Web/Telegrator.Hosting.Web.csproj +++ b/Telegrator.Hosting.Web/Telegrator.Hosting.Web.csproj @@ -4,18 +4,18 @@ net8.0 enable enable - True - True - - Telegrator : Telegram.Bot mediator framework - telegrator_nuget.png - https://github.com/Rikitav/Telegrator - telegram;bot;mediator;attributes;aspect;hosting;host;framework;easy;simple;handlers - True - True - LICENSE - README.md - 1.14.0 + True + True + + Telegrator : Telegram.Bot mediator framework + telegrator_nuget.png + https://github.com/Rikitav/Telegrator + telegram;bot;mediator;attributes;aspect;hosting;host;framework;easy;simple;handlers + True + True + LICENSE + README.md + 1.15.0 diff --git a/Telegrator.Hosting/Telegrator.Hosting.csproj b/Telegrator.Hosting/Telegrator.Hosting.csproj index 72a3e85..3f6aa92 100644 --- a/Telegrator.Hosting/Telegrator.Hosting.csproj +++ b/Telegrator.Hosting/Telegrator.Hosting.csproj @@ -16,7 +16,7 @@ True LICENSE README.md - 1.14.0 + 1.15.0 diff --git a/Telegrator.Tests/Telegrator.Tests.csproj b/Telegrator.Tests/Telegrator.Tests.csproj index 2a11e6c..ee19e6b 100644 --- a/Telegrator.Tests/Telegrator.Tests.csproj +++ b/Telegrator.Tests/Telegrator.Tests.csproj @@ -5,7 +5,6 @@ net8.0 enable enable - Debug;Release false diff --git a/Telegrator.sln b/Telegrator.sln index f9bed94..ec9992c 100644 --- a/Telegrator.sln +++ b/Telegrator.sln @@ -9,8 +9,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Telegrator.Hosting", "Teleg EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Telegrator.Tests", "Telegrator.Tests\Telegrator.Tests.csproj", "{0926C71D-FE0C-4963-B08B-1CBAFF1E3276}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Telegrator.Generators", "Telegrator.Generators\Telegrator.Generators.csproj", "{43927959-EB6D-4CBA-A652-2B7FC0C1DDA7}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{36D591C7-65C7-A0D1-1CBC-10CDE441BDC8}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig @@ -20,6 +18,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Telegrator.Analyzers", "Tel EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Telegrator.Hosting.Web", "Telegrator.Hosting.Web\Telegrator.Hosting.Web.csproj", "{98AB490F-6A36-CCFF-F6E6-B029D1665965}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Telegrator.RoslynGenerators", "dev\Telegrator.RoslynGenerators\Telegrator.RoslynGenerators.csproj", "{93658B7F-C651-4C78-2CB1-2C0AE00C45B5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Telegrator.RoslynExtensions", "dev\Telegrator.RoslynExtensions\Telegrator.RoslynExtensions.csproj", "{1E6980BE-32C1-A994-C329-B7C473411C87}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution AnalyzersDebug|Any CPU = AnalyzersDebug|Any CPU @@ -45,12 +47,6 @@ Global {0926C71D-FE0C-4963-B08B-1CBAFF1E3276}.Debug|Any CPU.Build.0 = Debug|Any CPU {0926C71D-FE0C-4963-B08B-1CBAFF1E3276}.Release|Any CPU.ActiveCfg = Release|Any CPU {0926C71D-FE0C-4963-B08B-1CBAFF1E3276}.Release|Any CPU.Build.0 = Release|Any CPU - {43927959-EB6D-4CBA-A652-2B7FC0C1DDA7}.AnalyzersDebug|Any CPU.ActiveCfg = AnalyzersDebug|Any CPU - {43927959-EB6D-4CBA-A652-2B7FC0C1DDA7}.AnalyzersDebug|Any CPU.Build.0 = AnalyzersDebug|Any CPU - {43927959-EB6D-4CBA-A652-2B7FC0C1DDA7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {43927959-EB6D-4CBA-A652-2B7FC0C1DDA7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {43927959-EB6D-4CBA-A652-2B7FC0C1DDA7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {43927959-EB6D-4CBA-A652-2B7FC0C1DDA7}.Release|Any CPU.Build.0 = Release|Any CPU {8B6A32EA-ECF7-4CAB-A1E5-2392063C986D}.AnalyzersDebug|Any CPU.ActiveCfg = Release|Any CPU {8B6A32EA-ECF7-4CAB-A1E5-2392063C986D}.AnalyzersDebug|Any CPU.Build.0 = Release|Any CPU {8B6A32EA-ECF7-4CAB-A1E5-2392063C986D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU @@ -63,6 +59,18 @@ Global {98AB490F-6A36-CCFF-F6E6-B029D1665965}.Debug|Any CPU.Build.0 = Debug|Any CPU {98AB490F-6A36-CCFF-F6E6-B029D1665965}.Release|Any CPU.ActiveCfg = Release|Any CPU {98AB490F-6A36-CCFF-F6E6-B029D1665965}.Release|Any CPU.Build.0 = Release|Any CPU + {93658B7F-C651-4C78-2CB1-2C0AE00C45B5}.AnalyzersDebug|Any CPU.ActiveCfg = AnalyzersDebug|Any CPU + {93658B7F-C651-4C78-2CB1-2C0AE00C45B5}.AnalyzersDebug|Any CPU.Build.0 = AnalyzersDebug|Any CPU + {93658B7F-C651-4C78-2CB1-2C0AE00C45B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {93658B7F-C651-4C78-2CB1-2C0AE00C45B5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {93658B7F-C651-4C78-2CB1-2C0AE00C45B5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {93658B7F-C651-4C78-2CB1-2C0AE00C45B5}.Release|Any CPU.Build.0 = Release|Any CPU + {1E6980BE-32C1-A994-C329-B7C473411C87}.AnalyzersDebug|Any CPU.ActiveCfg = AnalyzersDebug|Any CPU + {1E6980BE-32C1-A994-C329-B7C473411C87}.AnalyzersDebug|Any CPU.Build.0 = AnalyzersDebug|Any CPU + {1E6980BE-32C1-A994-C329-B7C473411C87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1E6980BE-32C1-A994-C329-B7C473411C87}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1E6980BE-32C1-A994-C329-B7C473411C87}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1E6980BE-32C1-A994-C329-B7C473411C87}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Telegrator/Markups/GeneratedInlineKeyboardMarkupAttributes.cs b/Telegrator/Markups/GeneratedInlineKeyboardMarkupAttributes.cs new file mode 100644 index 0000000..def6f8d --- /dev/null +++ b/Telegrator/Markups/GeneratedInlineKeyboardMarkupAttributes.cs @@ -0,0 +1,76 @@ +using Telegram.Bot.Types; +using Telegram.Bot.Types.ReplyMarkups; + +namespace Telegrator.Markups +{ + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + public sealed class CallbackButtonAttribute(string name, string data) : Attribute + { + public string Name { get; } = name; + public string Data { get; } = data; + } + + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + public sealed class GameButtonAttribute(string name, string data) : Attribute + { + public string Name { get; } = name; + public string Game { get; } = data; + } + + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + public sealed class CopyTextButtonAttribute(string name, CopyTextButton copyText) : Attribute + { + public string Name { get; } = name; + public CopyTextButton CopyText { get; } = copyText; + } + + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + public sealed class PayRequestButtonAttribute(string name) : Attribute + { + public string Name { get; } = name; + } + + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + public sealed class LoginButtonAttribute(string name, LoginUrl url) : Attribute + { + public string Name { get; } = name; + public LoginUrl Url { get; } = url; + } + + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + public sealed class UrlRedirectButtonAttribute(string name, string url) : Attribute + { + public string Name { get; } = name; + public string Url { get; } = url; + } + + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + public sealed class WebAppButtonAttribute(string name, WebAppInfo webApp) : Attribute + { + public string Name { get; } = name; + public WebAppInfo AppInfo { get; } = webApp; + } + + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + public sealed class SwitchQueryButtonAttribute(string name, string switchInlineQuery = "") : Attribute + { + public string Name { get; } = name; + public string Query { get; } = switchInlineQuery; + } + + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + public sealed class QueryCurrentButtonAttribute(string name, string switchInlineQueryCurrentChat = "") : Attribute + { + public string Name { get; } = name; + public string Query { get; } = switchInlineQueryCurrentChat; + } +} diff --git a/Telegrator/Markups/GeneratedReplyKeybooardMarkupAttributes.cs b/Telegrator/Markups/GeneratedReplyKeybooardMarkupAttributes.cs new file mode 100644 index 0000000..1a72633 --- /dev/null +++ b/Telegrator/Markups/GeneratedReplyKeybooardMarkupAttributes.cs @@ -0,0 +1,6 @@ +namespace Telegrator.Markups +{ + internal class GeneratedReplyKeybooardMarkupAttributes + { + } +} diff --git a/Telegrator/Telegrator.csproj b/Telegrator/Telegrator.csproj index 09c7bd1..42debc5 100644 --- a/Telegrator/Telegrator.csproj +++ b/Telegrator/Telegrator.csproj @@ -5,46 +5,37 @@ enable enable latest - Debug;Release + True True - - Telegrator : Telegram.Bot mediator framework - telegrator_nuget.png - README.md - https://github.com/Rikitav/Telegrator - telegram;bot;mediator;attributes;aspect;hosting;host;framework;easy;simple;handlers True True + + Telegrator : Telegram.Bot mediator framework + telegrator_nuget.png + https://github.com/Rikitav/Telegrator + telegram;bot;mediator;attributes;aspect;hosting;host;framework;easy;simple;handlers + README.md LICENSE - 1.14.0 + 1.15.0 - - True - \ - - - True - \ - - - True - \ - + + + - - - + + + - - + + diff --git a/dev/Telegrator.RoslynExtensions/CollectionsExtensions.cs b/dev/Telegrator.RoslynExtensions/CollectionsExtensions.cs new file mode 100644 index 0000000..ee4590a --- /dev/null +++ b/dev/Telegrator.RoslynExtensions/CollectionsExtensions.cs @@ -0,0 +1,64 @@ +namespace Telegrator.RoslynExtensions +{ + public static class CollectionsExtensions + { + public static IEnumerable Combine(params IEnumerable[] collections) + => collections.SelectMany(x => x); + + public static IEnumerable IntersectBy(this IEnumerable first, IEnumerable second, Func selector) + { + foreach (TSource item in first) + { + TValue value = selector(item); + if (second.Contains(value)) + yield return item; + } + } + + public static IList UnionAdd(this IList source, IEnumerable toUnion) + { + foreach (TValue toUnionValue in toUnion) + { + if (!source.Contains(toUnionValue, EqualityComparer.Default)) + source.Add(toUnionValue); + } + + return source; + } + + public static void UnionAdd(this ICollection collection, IEnumerable target) + { + foreach (TSource item in target) + { + if (!collection.Contains(item)) + collection.Add(item); + } + } + + public static void UnionAdd(this SortedList collection, IEnumerable target) + { + foreach (TSource item in target) + { + if (!collection.Values.Contains(item)) + collection.Add(item, item); + } + } + + public static int IndexOf(this IEnumerable source, Func predicate) + { + int index = 0; + foreach (T item in source) + { + if (predicate.Invoke(item)) + return index; + + index++; + } + + return -1; + } + + public static IEnumerable Repeat(this T item, int times) + => Enumerable.Range(0, times - 1).Select(_ => item); + } +} diff --git a/dev/Telegrator.RoslynExtensions/DiagnosticsHelper.cs b/dev/Telegrator.RoslynExtensions/DiagnosticsHelper.cs new file mode 100644 index 0000000..7f19ae5 --- /dev/null +++ b/dev/Telegrator.RoslynExtensions/DiagnosticsHelper.cs @@ -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); + } +} diff --git a/dev/Telegrator.RoslynExtensions/Exceptions.cs b/dev/Telegrator.RoslynExtensions/Exceptions.cs new file mode 100644 index 0000000..6cbc6d3 --- /dev/null +++ b/dev/Telegrator.RoslynExtensions/Exceptions.cs @@ -0,0 +1,7 @@ +namespace Telegrator.RoslynExtensions; + +public class TargteterNotFoundException() : Exception() { } + +public class BaseClassTypeNotFoundException() : Exception() { } + +public class AncestorNotFoundException : Exception { } diff --git a/Telegrator.Generators/GlobalSuppressions.cs b/dev/Telegrator.RoslynExtensions/GlobalSuppressions.cs similarity index 100% rename from Telegrator.Generators/GlobalSuppressions.cs rename to dev/Telegrator.RoslynExtensions/GlobalSuppressions.cs diff --git a/dev/Telegrator.RoslynExtensions/MemberDeclarationSyntaxExtensions.cs b/dev/Telegrator.RoslynExtensions/MemberDeclarationSyntaxExtensions.cs new file mode 100644 index 0000000..b8ca548 --- /dev/null +++ b/dev/Telegrator.RoslynExtensions/MemberDeclarationSyntaxExtensions.cs @@ -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(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(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)); + } +} diff --git a/dev/Telegrator.RoslynExtensions/StringBuilderExtensions.cs b/dev/Telegrator.RoslynExtensions/StringBuilderExtensions.cs new file mode 100644 index 0000000..be5f2a9 --- /dev/null +++ b/dev/Telegrator.RoslynExtensions/StringBuilderExtensions.cs @@ -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)); + } +} diff --git a/dev/Telegrator.RoslynExtensions/StringExtensions.cs b/dev/Telegrator.RoslynExtensions/StringExtensions.cs new file mode 100644 index 0000000..7967f54 --- /dev/null +++ b/dev/Telegrator.RoslynExtensions/StringExtensions.cs @@ -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); + } + } +} diff --git a/dev/Telegrator.RoslynExtensions/SymbolsExtensions.cs b/dev/Telegrator.RoslynExtensions/SymbolsExtensions.cs new file mode 100644 index 0000000..090169b --- /dev/null +++ b/dev/Telegrator.RoslynExtensions/SymbolsExtensions.cs @@ -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); + } +} diff --git a/dev/Telegrator.RoslynExtensions/SyntaxNodesExtensions.cs b/dev/Telegrator.RoslynExtensions/SyntaxNodesExtensions.cs new file mode 100644 index 0000000..00be76e --- /dev/null +++ b/dev/Telegrator.RoslynExtensions/SyntaxNodesExtensions.cs @@ -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(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(); + } + + public static bool TryFindAncestor(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 ToSeparatedSyntaxList(this IEnumerable elements) where TNode : SyntaxNode + => new SeparatedSyntaxList().AddRange(elements); + + public static SyntaxList ToSyntaxList(this IEnumerable elements) where TNode : SyntaxNode + => new SyntaxList().AddRange(elements); + } +} diff --git a/dev/Telegrator.RoslynExtensions/SyntaxTokenExtensions.cs b/dev/Telegrator.RoslynExtensions/SyntaxTokenExtensions.cs new file mode 100644 index 0000000..e59a245 --- /dev/null +++ b/dev/Telegrator.RoslynExtensions/SyntaxTokenExtensions.cs @@ -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; + } + } +} diff --git a/dev/Telegrator.RoslynExtensions/Telegrator.RoslynExtensions.csproj b/dev/Telegrator.RoslynExtensions/Telegrator.RoslynExtensions.csproj new file mode 100644 index 0000000..b26b1d8 --- /dev/null +++ b/dev/Telegrator.RoslynExtensions/Telegrator.RoslynExtensions.csproj @@ -0,0 +1,26 @@ + + + + netstandard2.0 + latest + enable + enable + true + False + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/Telegrator.Generators/ApiMarkdownGenerator.cs b/dev/Telegrator.RoslynGenerators/ApiMarkdownGenerator.cs similarity index 99% rename from Telegrator.Generators/ApiMarkdownGenerator.cs rename to dev/Telegrator.RoslynGenerators/ApiMarkdownGenerator.cs index 261a6b3..039d4b8 100644 --- a/Telegrator.Generators/ApiMarkdownGenerator.cs +++ b/dev/Telegrator.RoslynGenerators/ApiMarkdownGenerator.cs @@ -3,9 +3,9 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using System.Collections.Immutable; using System.Text; using System.Xml.Linq; +using Telegrator.RoslynExtensions; -#pragma warning disable CS0162 -namespace Telegrator.Generators +namespace Telegrator.RoslynGenerators { /// /// Source Generator для автоматической генерации Markdown-документации по публичному API Telegrator. diff --git a/dev/Telegrator.RoslynGenerators/GlobalSuppressions.cs b/dev/Telegrator.RoslynGenerators/GlobalSuppressions.cs new file mode 100644 index 0000000..361f387 --- /dev/null +++ b/dev/Telegrator.RoslynGenerators/GlobalSuppressions.cs @@ -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")] diff --git a/Telegrator.Generators/ImplicitHandlerBuilderExtensionsGenerator.cs b/dev/Telegrator.RoslynGenerators/ImplicitHandlerBuilderExtensionsGenerator.cs similarity index 97% rename from Telegrator.Generators/ImplicitHandlerBuilderExtensionsGenerator.cs rename to dev/Telegrator.RoslynGenerators/ImplicitHandlerBuilderExtensionsGenerator.cs index 9157517..8b637ae 100644 --- a/Telegrator.Generators/ImplicitHandlerBuilderExtensionsGenerator.cs +++ b/dev/Telegrator.RoslynGenerators/ImplicitHandlerBuilderExtensionsGenerator.cs @@ -2,19 +2,16 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using System.Collections.Immutable; -using System.Diagnostics; using System.Text; +using Telegrator.RoslynExtensions; -namespace Telegrator.Generators +namespace Telegrator.RoslynGenerators { [Generator(LanguageNames.CSharp)] public class ImplicitHandlerBuilderExtensionsGenerator : IIncrementalGenerator { public void Initialize(IncrementalGeneratorInitializationContext context) { -#if ANALYZERSDEBUG - Debugger.Launch(); -#endif IncrementalValueProvider> pipeline = context.SyntaxProvider .CreateSyntaxProvider(SyntaxPredicate, SyntaxTransform) .Where(declaration => declaration != null) @@ -68,7 +65,7 @@ namespace Telegrator.Generators { try { - usingDirectives.UnionAdd(classDeclaration.FindCompilationUnitSyntax().Usings.Select(use => use.ToString())); + usingDirectives.UnionAdd(classDeclaration.FindAncestor().Usings.Select(use => use.ToString())); ParseClassDeclaration(sourceBuilder, classDeclaration, targeters); } catch (TargteterNotFoundException) @@ -86,7 +83,7 @@ namespace Telegrator.Generators { try { - usingDirectives.UnionAdd(classDeclaration.FindCompilationUnitSyntax().Usings.Select(use => use.ToString())); + usingDirectives.UnionAdd(classDeclaration.FindAncestor().Usings.Select(use => use.ToString())); ParseClassDeclaration(sourceBuilder, classDeclaration, targeters); } catch (Exception exc) diff --git a/dev/Telegrator.RoslynGenerators/Telegrator.RoslynGenerators.csproj b/dev/Telegrator.RoslynGenerators/Telegrator.RoslynGenerators.csproj new file mode 100644 index 0000000..057ad41 --- /dev/null +++ b/dev/Telegrator.RoslynGenerators/Telegrator.RoslynGenerators.csproj @@ -0,0 +1,28 @@ + + + + netstandard2.0 + latest + enable + enable + true + True + True + true + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + +