From c809470fb0f4fd6695db4ee425fcd98465249f04 Mon Sep 17 00:00:00 2001 From: Rikitav Date: Mon, 18 Aug 2025 20:32:24 +0400 Subject: [PATCH] * Implicit handlers building extensions generator refactored * Telegram.Bot package updated --- .../DeveloperHelperAnalyzer.cs | 2 +- .../GeneratedKeyboardMarkupGenerator.cs | 56 +--- .../CollectionsExtensions.cs | 4 +- .../RoslynExtensions/DiagnosticsHelper.cs | 16 ++ .../RoslynExtensions/Exceptions.cs | 7 + .../MemberDeclarationSyntaxExtensions.cs | 15 +- .../StringBuilderExtensions.cs | 10 + .../RoslynExtensions/StringExtensions.cs | 21 ++ .../RoslynExtensions/SymbolsExtensions.cs | 28 ++ .../RoslynExtensions/SyntaxNodesExtensions.cs | 74 +++++ .../RoslynExtensions/SyntaxTokenExtensions.cs | 12 + .../Telegrator.Analyzers.csproj | 6 - Telegrator.sln | 14 +- Telegrator/Handlers/CallbackQueryHandler.cs | 9 +- Telegrator/Handlers/MessageHandler.cs | 31 +- Telegrator/Logging/Alligator.cs | 2 +- ...GeneratedInlineKeyboardMarkupAttributes.cs | 59 ++++ Telegrator/Polling/UpdateRouter.cs | 4 - Telegrator/Telegrator.csproj | 2 +- Telegrator/TypesExtensions.cs | 21 +- .../GlobalSuppressions.cs | 8 - .../Telegrator.RoslynExtensions.csproj | 26 -- ...plicitHandlerBuilderExtensionsGenerator.cs | 264 ++++++++++++++++-- .../RoslynExtensions/CollectionsExtensions.cs | 64 +++++ .../RoslynExtensions}/DiagnosticsHelper.cs | 0 .../RoslynExtensions}/Exceptions.cs | 0 .../MemberDeclarationSyntaxExtensions.cs | 39 +++ .../StringBuilderExtensions.cs | 0 .../RoslynExtensions}/StringExtensions.cs | 0 .../RoslynExtensions}/SymbolsExtensions.cs | 0 .../SyntaxNodesExtensions.cs | 0 .../SyntaxTokenExtensions.cs | 0 .../Telegrator.RoslynGenerators.csproj | 10 +- 33 files changed, 654 insertions(+), 150 deletions(-) rename {dev/Telegrator.RoslynExtensions => Telegrator.Analyzers/RoslynExtensions}/CollectionsExtensions.cs (94%) create mode 100644 Telegrator.Analyzers/RoslynExtensions/DiagnosticsHelper.cs create mode 100644 Telegrator.Analyzers/RoslynExtensions/Exceptions.cs rename {dev/Telegrator.RoslynExtensions => Telegrator.Analyzers/RoslynExtensions}/MemberDeclarationSyntaxExtensions.cs (65%) create mode 100644 Telegrator.Analyzers/RoslynExtensions/StringBuilderExtensions.cs create mode 100644 Telegrator.Analyzers/RoslynExtensions/StringExtensions.cs create mode 100644 Telegrator.Analyzers/RoslynExtensions/SymbolsExtensions.cs create mode 100644 Telegrator.Analyzers/RoslynExtensions/SyntaxNodesExtensions.cs create mode 100644 Telegrator.Analyzers/RoslynExtensions/SyntaxTokenExtensions.cs delete mode 100644 dev/Telegrator.RoslynExtensions/GlobalSuppressions.cs delete mode 100644 dev/Telegrator.RoslynExtensions/Telegrator.RoslynExtensions.csproj create mode 100644 dev/Telegrator.RoslynGenerators/RoslynExtensions/CollectionsExtensions.cs rename dev/{Telegrator.RoslynExtensions => Telegrator.RoslynGenerators/RoslynExtensions}/DiagnosticsHelper.cs (100%) rename dev/{Telegrator.RoslynExtensions => Telegrator.RoslynGenerators/RoslynExtensions}/Exceptions.cs (100%) create mode 100644 dev/Telegrator.RoslynGenerators/RoslynExtensions/MemberDeclarationSyntaxExtensions.cs rename dev/{Telegrator.RoslynExtensions => Telegrator.RoslynGenerators/RoslynExtensions}/StringBuilderExtensions.cs (100%) rename dev/{Telegrator.RoslynExtensions => Telegrator.RoslynGenerators/RoslynExtensions}/StringExtensions.cs (100%) rename dev/{Telegrator.RoslynExtensions => Telegrator.RoslynGenerators/RoslynExtensions}/SymbolsExtensions.cs (100%) rename dev/{Telegrator.RoslynExtensions => Telegrator.RoslynGenerators/RoslynExtensions}/SyntaxNodesExtensions.cs (100%) rename dev/{Telegrator.RoslynExtensions => Telegrator.RoslynGenerators/RoslynExtensions}/SyntaxTokenExtensions.cs (100%) diff --git a/Telegrator.Analyzers/DeveloperHelperAnalyzer.cs b/Telegrator.Analyzers/DeveloperHelperAnalyzer.cs index 7fd8271..0e12cbc 100644 --- a/Telegrator.Analyzers/DeveloperHelperAnalyzer.cs +++ b/Telegrator.Analyzers/DeveloperHelperAnalyzer.cs @@ -2,7 +2,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using System.Collections.Immutable; using System.Text; -using Telegrator.RoslynExtensions; +using Telegrator.Analyzers.RoslynExtensions; namespace Telegrator.Analyzers { diff --git a/Telegrator.Analyzers/GeneratedKeyboardMarkupGenerator.cs b/Telegrator.Analyzers/GeneratedKeyboardMarkupGenerator.cs index 0ab2479..7b96d3e 100644 --- a/Telegrator.Analyzers/GeneratedKeyboardMarkupGenerator.cs +++ b/Telegrator.Analyzers/GeneratedKeyboardMarkupGenerator.cs @@ -2,7 +2,8 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using System.Collections.Immutable; -using Telegrator.RoslynExtensions; +using Telegrator.Analyzers.RoslynExtensions; + #if DEBUG using System.Diagnostics; @@ -69,7 +70,11 @@ namespace Telegrator.Analyzers public void Initialize(IncrementalGeneratorInitializationContext context) { - IncrementalValueProvider> pipeline = context.SyntaxProvider.CreateSyntaxProvider(Provide, Transform).Where(x => x != null).Collect(); + IncrementalValueProvider> pipeline = context.SyntaxProvider + .CreateSyntaxProvider(Provide, Transform) + .Where(x => x != null) + .Collect(); + context.RegisterSourceOutput(pipeline, Execute); } @@ -183,21 +188,10 @@ namespace Telegrator.Analyzers try { - if (model.OriginalMethod.Parent is not ClassDeclarationSyntax containerClass) + if (model.OriginalMethod.Parent is not ClassDeclarationSyntax) 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)); - + NamespaceDeclarationSyntax genNamespace = GeneratedNamespaceDeclaration(model.OriginalMethod, [model.GeneratedField, model.GeneratedMethod]); compilationUnit = compilationUnit.AddMembers(genNamespace); } catch (Exception ex) @@ -210,30 +204,6 @@ namespace Telegrator.Analyzers 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) @@ -267,11 +237,11 @@ namespace Telegrator.Analyzers if (method.Parent is not ClassDeclarationSyntax containerClass) throw new MemberAccessException(); - int times = method.CountParentTree() - 1; + int times = method.CountParentTree(); ClassDeclarationSyntax generatedContainerClass = SyntaxFactory.ClassDeclaration(containerClass.Identifier) .WithMembers(new SyntaxList(generatedMembers.Select(member => member.DecorateMember(times + 1)))) .WithModifiers(containerClass.Modifiers.Decorate()) - .Decorate(times); + .DecorateType(times); MemberDeclarationSyntax generated = generatedContainerClass; MemberDeclarationSyntax inspecting = containerClass; @@ -290,7 +260,7 @@ namespace Telegrator.Analyzers generated = SyntaxFactory.ClassDeclaration(classDeclaration.Identifier) .WithMembers([generated]) .WithModifiers(classDeclaration.Modifiers.Decorate()) - .Decorate(times); + .DecorateType(times); break; } @@ -300,7 +270,7 @@ namespace Telegrator.Analyzers generated = SyntaxFactory.StructDeclaration(structDeclaration.Identifier) .WithMembers([generated]) .WithModifiers(structDeclaration.Modifiers.Decorate()) - .Decorate(times); + .DecorateType(times); break; } diff --git a/dev/Telegrator.RoslynExtensions/CollectionsExtensions.cs b/Telegrator.Analyzers/RoslynExtensions/CollectionsExtensions.cs similarity index 94% rename from dev/Telegrator.RoslynExtensions/CollectionsExtensions.cs rename to Telegrator.Analyzers/RoslynExtensions/CollectionsExtensions.cs index ee4590a..ec2f28e 100644 --- a/dev/Telegrator.RoslynExtensions/CollectionsExtensions.cs +++ b/Telegrator.Analyzers/RoslynExtensions/CollectionsExtensions.cs @@ -1,4 +1,4 @@ -namespace Telegrator.RoslynExtensions +namespace Telegrator.Analyzers.RoslynExtensions { public static class CollectionsExtensions { @@ -59,6 +59,6 @@ } public static IEnumerable Repeat(this T item, int times) - => Enumerable.Range(0, times - 1).Select(_ => item); + => Enumerable.Range(0, times).Select(_ => item); } } diff --git a/Telegrator.Analyzers/RoslynExtensions/DiagnosticsHelper.cs b/Telegrator.Analyzers/RoslynExtensions/DiagnosticsHelper.cs new file mode 100644 index 0000000..09ab39e --- /dev/null +++ b/Telegrator.Analyzers/RoslynExtensions/DiagnosticsHelper.cs @@ -0,0 +1,16 @@ +using Microsoft.CodeAnalysis; + +namespace Telegrator.Analyzers.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/Telegrator.Analyzers/RoslynExtensions/Exceptions.cs b/Telegrator.Analyzers/RoslynExtensions/Exceptions.cs new file mode 100644 index 0000000..7124a8b --- /dev/null +++ b/Telegrator.Analyzers/RoslynExtensions/Exceptions.cs @@ -0,0 +1,7 @@ +namespace Telegrator.Analyzers.RoslynExtensions; + +public class TargteterNotFoundException() : Exception() { } + +public class BaseClassTypeNotFoundException() : Exception() { } + +public class AncestorNotFoundException : Exception { } diff --git a/dev/Telegrator.RoslynExtensions/MemberDeclarationSyntaxExtensions.cs b/Telegrator.Analyzers/RoslynExtensions/MemberDeclarationSyntaxExtensions.cs similarity index 65% rename from dev/Telegrator.RoslynExtensions/MemberDeclarationSyntaxExtensions.cs rename to Telegrator.Analyzers/RoslynExtensions/MemberDeclarationSyntaxExtensions.cs index b8ca548..266e456 100644 --- a/dev/Telegrator.RoslynExtensions/MemberDeclarationSyntaxExtensions.cs +++ b/Telegrator.Analyzers/RoslynExtensions/MemberDeclarationSyntaxExtensions.cs @@ -2,19 +2,26 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -namespace Telegrator.RoslynExtensions +namespace Telegrator.Analyzers.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 + public static BlockSyntax DecorateBlock(this BlockSyntax block, int times) => block + .WithStatements([.. block.Statements.Select(statement => statement.DecorateStatememnt(times + 1))]) + .WithOpenBraceToken(SyntaxFactory.Token(SyntaxKind.OpenBraceToken).WithLeadingTrivia(TabulationTrivia.Repeat(times)).WithTrailingTrivia(NewLineTrivia)) + .WithCloseBraceToken(SyntaxFactory.Token(SyntaxKind.CloseBraceToken).WithLeadingTrivia(TabulationTrivia.Repeat(times)).WithTrailingTrivia(NewLineTrivia)); + + public static T DecorateStatememnt(this T statememnt, int times) where T : StatementSyntax => statememnt + .WithoutTrivia().WithLeadingTrivia(TabulationTrivia.Repeat(times)).WithTrailingTrivia(NewLineTrivia); + + public static T DecorateMember(this T typeDeclaration, int times) where T : MemberDeclarationSyntax => typeDeclaration .WithoutTrivia().WithLeadingTrivia(TabulationTrivia.Repeat(times)).WithTrailingTrivia(NewLineTrivia); public static NamespaceDeclarationSyntax Decorate(this NamespaceDeclarationSyntax namespaceDeclaration) => namespaceDeclaration @@ -22,7 +29,7 @@ namespace Telegrator.RoslynExtensions .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 + public static T DecorateType(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)) diff --git a/Telegrator.Analyzers/RoslynExtensions/StringBuilderExtensions.cs b/Telegrator.Analyzers/RoslynExtensions/StringBuilderExtensions.cs new file mode 100644 index 0000000..067c0c1 --- /dev/null +++ b/Telegrator.Analyzers/RoslynExtensions/StringBuilderExtensions.cs @@ -0,0 +1,10 @@ +using System.Text; + +namespace Telegrator.Analyzers.RoslynExtensions +{ + public static class StringBuilderExtensions + { + public static StringBuilder AppendTabs(this StringBuilder builder, int count) + => builder.Append(new string('\t', count)); + } +} diff --git a/Telegrator.Analyzers/RoslynExtensions/StringExtensions.cs b/Telegrator.Analyzers/RoslynExtensions/StringExtensions.cs new file mode 100644 index 0000000..43e0e2c --- /dev/null +++ b/Telegrator.Analyzers/RoslynExtensions/StringExtensions.cs @@ -0,0 +1,21 @@ +namespace Telegrator.Analyzers.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/Telegrator.Analyzers/RoslynExtensions/SymbolsExtensions.cs b/Telegrator.Analyzers/RoslynExtensions/SymbolsExtensions.cs new file mode 100644 index 0000000..daff52e --- /dev/null +++ b/Telegrator.Analyzers/RoslynExtensions/SymbolsExtensions.cs @@ -0,0 +1,28 @@ +using Microsoft.CodeAnalysis; + +namespace Telegrator.Analyzers.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/Telegrator.Analyzers/RoslynExtensions/SyntaxNodesExtensions.cs b/Telegrator.Analyzers/RoslynExtensions/SyntaxNodesExtensions.cs new file mode 100644 index 0000000..35c7423 --- /dev/null +++ b/Telegrator.Analyzers/RoslynExtensions/SyntaxNodesExtensions.cs @@ -0,0 +1,74 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Telegrator.Analyzers.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/Telegrator.Analyzers/RoslynExtensions/SyntaxTokenExtensions.cs b/Telegrator.Analyzers/RoslynExtensions/SyntaxTokenExtensions.cs new file mode 100644 index 0000000..a0d4555 --- /dev/null +++ b/Telegrator.Analyzers/RoslynExtensions/SyntaxTokenExtensions.cs @@ -0,0 +1,12 @@ +using Microsoft.CodeAnalysis; + +namespace Telegrator.Analyzers.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/Telegrator.Analyzers/Telegrator.Analyzers.csproj b/Telegrator.Analyzers/Telegrator.Analyzers.csproj index d00dae0..8fe7cfc 100644 --- a/Telegrator.Analyzers/Telegrator.Analyzers.csproj +++ b/Telegrator.Analyzers/Telegrator.Analyzers.csproj @@ -6,7 +6,6 @@ enable enable true - true @@ -22,10 +21,5 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - diff --git a/Telegrator.sln b/Telegrator.sln index ec9992c..9ac3a7a 100644 --- a/Telegrator.sln +++ b/Telegrator.sln @@ -20,7 +20,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Telegrator.Hosting.Web", "T 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}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SosalBot", "..\SosalBot\SosalBot\SosalBot.csproj", "{D6AA4D47-0DCE-520E-5779-A14EA9CB1DEC}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -65,12 +65,12 @@ Global {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 + {D6AA4D47-0DCE-520E-5779-A14EA9CB1DEC}.AnalyzersDebug|Any CPU.ActiveCfg = Release|Any CPU + {D6AA4D47-0DCE-520E-5779-A14EA9CB1DEC}.AnalyzersDebug|Any CPU.Build.0 = Release|Any CPU + {D6AA4D47-0DCE-520E-5779-A14EA9CB1DEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D6AA4D47-0DCE-520E-5779-A14EA9CB1DEC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D6AA4D47-0DCE-520E-5779-A14EA9CB1DEC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D6AA4D47-0DCE-520E-5779-A14EA9CB1DEC}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Telegrator/Handlers/CallbackQueryHandler.cs b/Telegrator/Handlers/CallbackQueryHandler.cs index 0d6d9fa..22f2310 100644 --- a/Telegrator/Handlers/CallbackQueryHandler.cs +++ b/Telegrator/Handlers/CallbackQueryHandler.cs @@ -57,6 +57,8 @@ namespace Telegrator.Handlers /// The message effect ID. /// The business connection ID. /// Whether to allow paid broadcast. + /// + /// /// The cancellation token. /// The sent message. protected async Task Responce( @@ -72,6 +74,8 @@ namespace Telegrator.Handlers string? messageEffectId = null, string? businessConnectionId = null, bool allowPaidBroadcast = false, + int? directMessageTopicId = null, + SuggestedPostParameters? suggestedPostParameters = null, CancellationToken cancellationToken = default) => await Container.Responce( text, parseMode, replyParameters, @@ -79,8 +83,9 @@ namespace Telegrator.Handlers messageThreadId, entities, disableNotification, protectContent, messageEffectId, businessConnectionId, - allowPaidBroadcast, cancellationToken); - + allowPaidBroadcast, directMessageTopicId, + suggestedPostParameters, cancellationToken); + /// /// Edits the current callback message with new text. /// diff --git a/Telegrator/Handlers/MessageHandler.cs b/Telegrator/Handlers/MessageHandler.cs index f840ee8..753feb5 100644 --- a/Telegrator/Handlers/MessageHandler.cs +++ b/Telegrator/Handlers/MessageHandler.cs @@ -1,5 +1,4 @@ -using Telegram.Bot; -using Telegram.Bot.Types; +using Telegram.Bot.Types; using Telegram.Bot.Types.Enums; using Telegram.Bot.Types.ReplyMarkups; using Telegrator.Attributes; @@ -42,6 +41,8 @@ namespace Telegrator.Handlers /// The message effect ID. /// The business connection ID. /// Whether to allow paid broadcast. + /// + /// /// The cancellation token. /// The sent message. protected async Task Reply( @@ -56,6 +57,8 @@ namespace Telegrator.Handlers string? messageEffectId = null, string? businessConnectionId = null, bool allowPaidBroadcast = false, + int? directMessageTopicId = null, + SuggestedPostParameters? suggestedPostParameters = null, CancellationToken cancellationToken = default) => await Container.Reply( text, parseMode, @@ -63,7 +66,8 @@ namespace Telegrator.Handlers messageThreadId, entities, disableNotification, protectContent, messageEffectId, businessConnectionId, - allowPaidBroadcast, cancellationToken); + allowPaidBroadcast, directMessageTopicId, + suggestedPostParameters, cancellationToken); /// /// Sends a response message to the current chat. @@ -80,6 +84,8 @@ namespace Telegrator.Handlers /// The message effect ID. /// The business connection ID. /// Whether to allow paid broadcast. + /// + /// /// The cancellation token. /// The sent message. protected async Task Responce( @@ -95,6 +101,8 @@ namespace Telegrator.Handlers string? messageEffectId = null, string? businessConnectionId = null, bool allowPaidBroadcast = false, + int? directMessageTopicId = null, + SuggestedPostParameters? suggestedPostParameters = null, CancellationToken cancellationToken = default) => await Container.Responce( text, parseMode, replyParameters, @@ -102,7 +110,8 @@ namespace Telegrator.Handlers messageThreadId, entities, disableNotification, protectContent, messageEffectId, businessConnectionId, - allowPaidBroadcast, cancellationToken); + allowPaidBroadcast, directMessageTopicId, + suggestedPostParameters, cancellationToken); } /// @@ -125,6 +134,8 @@ namespace Telegrator.Handlers /// The message effect ID. /// The business connection ID. /// Whether to allow paid broadcast. + /// + /// /// The cancellation token. /// The sent message. protected async Task Reply( @@ -139,6 +150,8 @@ namespace Telegrator.Handlers string? messageEffectId = null, string? businessConnectionId = null, bool allowPaidBroadcast = false, + int? directMessageTopicId = null, + SuggestedPostParameters? suggestedPostParameters = null, CancellationToken cancellationToken = default) => await Container.Reply( text, parseMode, @@ -146,7 +159,8 @@ namespace Telegrator.Handlers messageThreadId, entities, disableNotification, protectContent, messageEffectId, businessConnectionId, - allowPaidBroadcast, cancellationToken); + allowPaidBroadcast, directMessageTopicId, + suggestedPostParameters, cancellationToken); /// /// Sends a response message to the current chat. @@ -163,6 +177,8 @@ namespace Telegrator.Handlers /// The message effect ID. /// The business connection ID. /// Whether to allow paid broadcast. + /// + /// /// The cancellation token. /// The sent message. protected async Task Responce( @@ -178,6 +194,8 @@ namespace Telegrator.Handlers string? messageEffectId = null, string? businessConnectionId = null, bool allowPaidBroadcast = false, + int? directMessageTopicId = null, + SuggestedPostParameters? suggestedPostParameters = null, CancellationToken cancellationToken = default) => await Container.Responce( text, parseMode, replyParameters, @@ -185,6 +203,7 @@ namespace Telegrator.Handlers messageThreadId, entities, disableNotification, protectContent, messageEffectId, businessConnectionId, - allowPaidBroadcast, cancellationToken); + allowPaidBroadcast, directMessageTopicId, + suggestedPostParameters, cancellationToken); } } diff --git a/Telegrator/Logging/Alligator.cs b/Telegrator/Logging/Alligator.cs index f044951..b63b776 100644 --- a/Telegrator/Logging/Alligator.cs +++ b/Telegrator/Logging/Alligator.cs @@ -20,7 +20,7 @@ namespace Telegrator.Logging /// Minimal level of logging messages. /// Any messages below thi value will not be writen! /// - public static LogLevel MinimalLevel { get; set; } + public static LogLevel MinimalLevel { get; set; } = LogLevel.Information; /// /// Adds a logger adapter to the centralized logging system. diff --git a/Telegrator/Markups/GeneratedInlineKeyboardMarkupAttributes.cs b/Telegrator/Markups/GeneratedInlineKeyboardMarkupAttributes.cs index def6f8d..9e726ff 100644 --- a/Telegrator/Markups/GeneratedInlineKeyboardMarkupAttributes.cs +++ b/Telegrator/Markups/GeneratedInlineKeyboardMarkupAttributes.cs @@ -7,7 +7,14 @@ namespace Telegrator.Markups [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] public sealed class CallbackButtonAttribute(string name, string data) : Attribute { + /// + /// Name of button + /// public string Name { get; } = name; + + /// + /// Data that will be send to bot + /// public string Data { get; } = data; } @@ -15,7 +22,14 @@ namespace Telegrator.Markups [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] public sealed class GameButtonAttribute(string name, string data) : Attribute { + /// + /// Name of button + /// public string Name { get; } = name; + + /// + /// Game to open + /// public string Game { get; } = data; } @@ -23,7 +37,14 @@ namespace Telegrator.Markups [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] public sealed class CopyTextButtonAttribute(string name, CopyTextButton copyText) : Attribute { + /// + /// Name of button + /// public string Name { get; } = name; + + /// + /// Text to copy + /// public CopyTextButton CopyText { get; } = copyText; } @@ -31,6 +52,9 @@ namespace Telegrator.Markups [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] public sealed class PayRequestButtonAttribute(string name) : Attribute { + /// + /// Name of button + /// public string Name { get; } = name; } @@ -38,7 +62,14 @@ namespace Telegrator.Markups [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] public sealed class LoginButtonAttribute(string name, LoginUrl url) : Attribute { + /// + /// Name of button + /// public string Name { get; } = name; + + /// + /// Url of app to login to + /// public LoginUrl Url { get; } = url; } @@ -46,7 +77,14 @@ namespace Telegrator.Markups [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] public sealed class UrlRedirectButtonAttribute(string name, string url) : Attribute { + /// + /// Name of button + /// public string Name { get; } = name; + + /// + /// Url to redirect user + /// public string Url { get; } = url; } @@ -54,7 +92,14 @@ namespace Telegrator.Markups [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] public sealed class WebAppButtonAttribute(string name, WebAppInfo webApp) : Attribute { + /// + /// Name of button + /// public string Name { get; } = name; + + /// + /// Info about mini app to open + /// public WebAppInfo AppInfo { get; } = webApp; } @@ -62,7 +107,14 @@ namespace Telegrator.Markups [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] public sealed class SwitchQueryButtonAttribute(string name, string switchInlineQuery = "") : Attribute { + /// + /// Name of button + /// public string Name { get; } = name; + + /// + /// Query + /// public string Query { get; } = switchInlineQuery; } @@ -70,7 +122,14 @@ namespace Telegrator.Markups [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] public sealed class QueryCurrentButtonAttribute(string name, string switchInlineQueryCurrentChat = "") : Attribute { + /// + /// Name of button + /// public string Name { get; } = name; + + /// + /// Query + /// public string Query { get; } = switchInlineQueryCurrentChat; } } diff --git a/Telegrator/Polling/UpdateRouter.cs b/Telegrator/Polling/UpdateRouter.cs index c46e23f..e6088a6 100644 --- a/Telegrator/Polling/UpdateRouter.cs +++ b/Telegrator/Polling/UpdateRouter.cs @@ -158,10 +158,6 @@ namespace Telegrator.Polling return []; } - //IEnumerable described = DescribeDescriptors(provider, descriptors, updateRouter, client, update, cancellationToken); - //Alligator.RouterWriteLine("Described total of {0} handlers for Update ({1}) from {2} provider", described.Count(), update.Id, provider.GetType().Name); - //Alligator.RouterWriteLine("Described handlers : {0}", string.Join(", ", described)); - return DescribeDescriptors(provider, descriptors, client, update, cancellationToken); } diff --git a/Telegrator/Telegrator.csproj b/Telegrator/Telegrator.csproj index 42debc5..bc93138 100644 --- a/Telegrator/Telegrator.csproj +++ b/Telegrator/Telegrator.csproj @@ -27,7 +27,7 @@ - + diff --git a/Telegrator/TypesExtensions.cs b/Telegrator/TypesExtensions.cs index ca53be1..8e8c2a3 100644 --- a/Telegrator/TypesExtensions.cs +++ b/Telegrator/TypesExtensions.cs @@ -225,6 +225,8 @@ namespace Telegrator /// The message effect ID. /// The business connection ID. /// Whether to allow paid broadcast. + /// + /// /// The cancellation token. /// The sent message. public static async Task Reply( @@ -240,6 +242,8 @@ namespace Telegrator string? messageEffectId = null, string? businessConnectionId = null, bool allowPaidBroadcast = false, + int? directMessageTopicId = null, + SuggestedPostParameters? suggestedPostParameters = null, CancellationToken cancellationToken = default) => await container.Client.SendMessage( container.ActualUpdate.Chat, text, parseMode, container.ActualUpdate, @@ -247,7 +251,8 @@ namespace Telegrator messageThreadId, entities, disableNotification, protectContent, messageEffectId, businessConnectionId, - allowPaidBroadcast, cancellationToken); + allowPaidBroadcast, directMessageTopicId, + suggestedPostParameters, cancellationToken); /// /// Sends a response message to the current chat. @@ -265,6 +270,8 @@ namespace Telegrator /// The message effect ID. /// The business connection ID. /// Whether to allow paid broadcast. + /// + /// /// The cancellation token. /// The sent message. public static async Task Responce( @@ -281,6 +288,8 @@ namespace Telegrator string? messageEffectId = null, string? businessConnectionId = null, bool allowPaidBroadcast = false, + int? directMessageTopicId = null, + SuggestedPostParameters? suggestedPostParameters = null, CancellationToken cancellationToken = default) => await container.Client.SendMessage( container.ActualUpdate.Chat, text, parseMode, replyParameters, @@ -288,7 +297,8 @@ namespace Telegrator messageThreadId, entities, disableNotification, protectContent, messageEffectId, businessConnectionId, - allowPaidBroadcast, cancellationToken); + allowPaidBroadcast, directMessageTopicId, + suggestedPostParameters, cancellationToken); /// /// Responnces to message that this CallbackQuery was originated from @@ -306,6 +316,8 @@ namespace Telegrator /// /// /// + /// + /// /// /// /// @@ -323,6 +335,8 @@ namespace Telegrator string? messageEffectId = null, string? businessConnectionId = null, bool allowPaidBroadcast = false, + int? directMessageTopicId = null, + SuggestedPostParameters? suggestedPostParameters = null, CancellationToken cancellationToken = default) { CallbackQuery query = container.ActualUpdate; @@ -335,7 +349,8 @@ namespace Telegrator messageThreadId, entities, disableNotification, protectContent, messageEffectId, businessConnectionId, - allowPaidBroadcast, cancellationToken); + allowPaidBroadcast, directMessageTopicId, + suggestedPostParameters, cancellationToken); } /// diff --git a/dev/Telegrator.RoslynExtensions/GlobalSuppressions.cs b/dev/Telegrator.RoslynExtensions/GlobalSuppressions.cs deleted file mode 100644 index 361f387..0000000 --- a/dev/Telegrator.RoslynExtensions/GlobalSuppressions.cs +++ /dev/null @@ -1,8 +0,0 @@ -// 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/dev/Telegrator.RoslynExtensions/Telegrator.RoslynExtensions.csproj b/dev/Telegrator.RoslynExtensions/Telegrator.RoslynExtensions.csproj deleted file mode 100644 index b26b1d8..0000000 --- a/dev/Telegrator.RoslynExtensions/Telegrator.RoslynExtensions.csproj +++ /dev/null @@ -1,26 +0,0 @@ - - - - netstandard2.0 - latest - enable - enable - true - False - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - diff --git a/dev/Telegrator.RoslynGenerators/ImplicitHandlerBuilderExtensionsGenerator.cs b/dev/Telegrator.RoslynGenerators/ImplicitHandlerBuilderExtensionsGenerator.cs index 8b637ae..1f85be5 100644 --- a/dev/Telegrator.RoslynGenerators/ImplicitHandlerBuilderExtensionsGenerator.cs +++ b/dev/Telegrator.RoslynGenerators/ImplicitHandlerBuilderExtensionsGenerator.cs @@ -4,14 +4,36 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using System.Collections.Immutable; using System.Text; using Telegrator.RoslynExtensions; +using Telegrator.RoslynGenerators.RoslynExtensions; + +#if DEBUG +using System.Diagnostics; +#endif namespace Telegrator.RoslynGenerators { [Generator(LanguageNames.CSharp)] public class ImplicitHandlerBuilderExtensionsGenerator : IIncrementalGenerator { + private static readonly string[] DefaultUsings = + [ + "Telegrator.Handlers.Building", + "Telegrator.Handlers.Building.Components" + ]; + + private static readonly ParameterSyntax ExtensionMethodThisParam = SyntaxFactory.Parameter(SyntaxFactory.Identifier("builder")).WithType(SyntaxFactory.IdentifierName("TBuilder").WithLeadingTrivia(SyntaxFactory.SyntaxTrivia(SyntaxKind.WhitespaceTrivia, " ")).WithTrailingTrivia(WhitespaceTrivia)).WithModifiers([SyntaxFactory.Token(SyntaxKind.ThisKeyword)]); + private static readonly MemberAccessExpressionSyntax BuilderAdderMethodAccessExpression = SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, SyntaxFactory.IdentifierName("builder"), SyntaxFactory.IdentifierName("AddTargetedFilters")); + private static readonly IEqualityComparer UsingEqualityComparer = new UsingDirectiveEqualityComparer(); + + 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"); + public void Initialize(IncrementalGeneratorInitializationContext context) { +#if DEBUG + Debugger.Launch(); +#endif IncrementalValueProvider> pipeline = context.SyntaxProvider .CreateSyntaxProvider(SyntaxPredicate, SyntaxTransform) .Where(declaration => declaration != null) @@ -20,12 +42,10 @@ namespace Telegrator.RoslynGenerators context.RegisterImplementationSourceOutput(pipeline, GenerateSource); } - private static bool SyntaxPredicate(SyntaxNode node, CancellationToken _) + private static bool SyntaxPredicate(SyntaxNode node, CancellationToken cancellationToken) { - if (node is not ClassDeclarationSyntax) - return false; - - return true; + cancellationToken.ThrowIfCancellationRequested(); + return node is ClassDeclarationSyntax; } private static ClassDeclarationSyntax SyntaxTransform(GeneratorSyntaxContext context, CancellationToken _) @@ -45,59 +65,246 @@ namespace Telegrator.RoslynGenerators private static void GenerateSource(SourceProductionContext context, ImmutableArray declarations) { - StringBuilder source = new StringBuilder(); + StringBuilder debugExport = new StringBuilder("/*"); + List usings = ParseUsings(DefaultUsings).ToList(); + + /* Dictionary targeters = []; List 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 lateTargeterClasses = []; + Dictionary targetters = []; foreach (ClassDeclarationSyntax classDeclaration in declarations) { try { - usingDirectives.UnionAdd(classDeclaration.FindAncestor().Usings.Select(use => use.ToString())); - ParseClassDeclaration(sourceBuilder, classDeclaration, targeters); - } - catch (TargteterNotFoundException) - { - lateTargeterClasses.Add(classDeclaration); + string className = classDeclaration.Identifier.ToString(); + if (className == "FilterAnnotation") + continue; + + MethodDeclarationSyntax? targeter = classDeclaration.Members.OfType().SingleOrDefault(IsTargeterMethod); + if (targeter != null) + { + try + { + MethodDeclarationSyntax genTargeter = GenerateTargetterMethod(classDeclaration, targeter); + targetters.Add(className, genTargeter); + } + catch (Exception exc) + { + string errorFormat = string.Format("\nFailed to generate for {0} : {1}\n", classDeclaration.Identifier.ToString(), exc.ToString()); + debugExport.AppendLine(errorFormat); + } + } } catch (Exception exc) { - string errorFormat = string.Format("\t\t// failed to generate for {0} : {1}", classDeclaration.Identifier.ToString(), exc.GetType().Name); - sourceBuilder.AppendLine(errorFormat); + string errorFormat = string.Format("\nFailed to generate for {0} : {1}\n", classDeclaration.Identifier.ToString(), exc.ToString()); + debugExport.AppendLine(errorFormat); } } - - foreach (ClassDeclarationSyntax classDeclaration in lateTargeterClasses) + + List extensions = []; + foreach (ClassDeclarationSyntax classDeclaration in declarations) { - try + if (classDeclaration.Modifiers.HasModifiers("abstract")) + continue; + + usings.UnionAdd(classDeclaration.FindAncestor().Usings, UsingEqualityComparer); + MethodDeclarationSyntax targeter = FindTargetterMethod(targetters, classDeclaration); + + if (classDeclaration.ParameterList != null && classDeclaration.BaseList != null) { - usingDirectives.UnionAdd(classDeclaration.FindAncestor().Usings.Select(use => use.ToString())); - ParseClassDeclaration(sourceBuilder, classDeclaration, targeters); + try + { + PrimaryConstructorBaseTypeSyntax primaryConstructor = (PrimaryConstructorBaseTypeSyntax)classDeclaration.BaseList.Types.ElementAt(0); + MethodDeclarationSyntax genExtension = GeneratedExtensionsMethod(classDeclaration, classDeclaration.ParameterList, primaryConstructor.ArgumentList, targeter); + extensions.Add(genExtension); + } + catch (Exception exc) + { + string errorFormat = string.Format("\nFailed to generate for {0} : {1}\n", classDeclaration.Identifier.ToString(), exc.ToString()); + debugExport.AppendLine(errorFormat); + } } - catch (Exception exc) + + foreach (ConstructorDeclarationSyntax ctor in GetConstructors(classDeclaration)) { - string errorFormat = string.Format("\t\t// failed to generate for {0} : {1}", classDeclaration.Identifier.ToString(), exc.GetType().Name); - sourceBuilder.AppendLine(errorFormat); + try + { + if (ctor.Initializer == null) + continue; + + MethodDeclarationSyntax genExtension = GeneratedExtensionsMethod(classDeclaration, ctor.ParameterList, ctor.Initializer.ArgumentList, targeter); + extensions.Add(genExtension); + } + catch (Exception exc) + { + string errorFormat = string.Format("\nFailed to generate for {0} : {1}\n", classDeclaration.Identifier.ToString(), exc.ToString()); + debugExport.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()); + try + { + ClassDeclarationSyntax extensionsClass = SyntaxFactory.ClassDeclaration("HandlerBuilderExtensions") + .WithModifiers(Modifiers(SyntaxKind.PublicKeyword, SyntaxKind.StaticKeyword, SyntaxKind.PartialKeyword)) + .AddMembers([.. targetters.Values, .. extensions]) + .DecorateType(1); + + NamespaceDeclarationSyntax namespaceDeclaration = SyntaxFactory.NamespaceDeclaration(SyntaxFactory.ParseName("Telegrator")) + .WithMembers([extensionsClass]) + .Decorate(); + + CompilationUnitSyntax compilationUnit = SyntaxFactory.CompilationUnit() + .WithUsings([.. usings]) + .WithMembers([namespaceDeclaration]); + + context.AddSource("GeneratedHandlerBuilderExtensions.cs", compilationUnit.ToFullString()); + } + catch (Exception exc) + { + string errorFormat = string.Format("\nFailed to generate : {0}\n", exc.ToString()); + debugExport.AppendLine(errorFormat); + } + + context.AddSource("GeneratedHandlerBuilderExtensions.Debug.cs", debugExport.AppendLine("*/").ToString()); } + private static MethodDeclarationSyntax GenerateTargetterMethod(ClassDeclarationSyntax classDeclaration, MethodDeclarationSyntax targetterMethod) + { + SyntaxToken identifier = SyntaxFactory.Identifier(classDeclaration.Identifier.ToString() + "_" + targetterMethod.Identifier.ToString()); + MethodDeclarationSyntax method = SyntaxFactory.MethodDeclaration(targetterMethod.ReturnType, identifier) + .WithParameterList(targetterMethod.ParameterList) + .WithModifiers(Modifiers(SyntaxKind.PrivateKeyword, SyntaxKind.StaticKeyword)); + + if (targetterMethod.Body != null) + method = method.WithBody(targetterMethod.Body); + + if (targetterMethod.ExpressionBody != null) + method = method.WithExpressionBody(targetterMethod.ExpressionBody).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); + + return method.DecorateMember(2); + } + + private static MethodDeclarationSyntax GeneratedExtensionsMethod(ClassDeclarationSyntax classDeclaration, ParameterListSyntax methodParameters, ArgumentListSyntax invokerArguments, MethodDeclarationSyntax targetterMethod) + { + ParameterListSyntax parameters = SyntaxFactory.ParameterList([ExtensionMethodThisParam, ..methodParameters.Parameters]); + TypeParameterListSyntax typeParameters = SyntaxFactory.TypeParameterList([SyntaxFactory.TypeParameter("TBuilder")]); + + InvocationExpressionSyntax invocationExpression = SyntaxFactory.InvocationExpression(BuilderAdderMethodAccessExpression, AddTargeter(invokerArguments, targetterMethod)); + BlockSyntax body = SyntaxFactory.Block(new StatementSyntax[] + { + SyntaxFactory.ExpressionStatement(invocationExpression), + SyntaxFactory.ReturnStatement(SyntaxFactory.IdentifierName("builder").WithLeadingTrivia(WhitespaceTrivia)) + }); + + TypeParameterConstraintClauseSyntax typeParameterConstraint = SyntaxFactory.TypeParameterConstraintClause(SyntaxFactory.IdentifierName("TBuilder").WithLeadingTrivia(WhitespaceTrivia).WithTrailingTrivia(WhitespaceTrivia)) + .WithConstraints([SyntaxFactory.TypeConstraint(SyntaxFactory.ParseTypeName("IHandlerBuilder").WithLeadingTrivia(WhitespaceTrivia))]) + .WithLeadingTrivia(WhitespaceTrivia); + + string filterName = classDeclaration.Identifier.ToString().Replace("Attribute", string.Empty); + if (filterName == "ChatType") + filterName = "InChatType"; // Because it conflicting + + SyntaxToken identifier = SyntaxFactory.Identifier(filterName); + TypeSyntax returnType = SyntaxFactory.ParseTypeName("TBuilder").WithTrailingTrivia(WhitespaceTrivia); + SyntaxTriviaList xmlDoc = BuildExtensionXmlDocTrivia(classDeclaration, methodParameters); + + MethodDeclarationSyntax method = SyntaxFactory.MethodDeclaration(returnType, identifier) + .WithParameterList(parameters) + .WithBody(body.DecorateBlock(2)) + .WithTypeParameterList(typeParameters) + .WithModifiers(Modifiers(SyntaxKind.PublicKeyword, SyntaxKind.StaticKeyword)) + .WithConstraintClauses([typeParameterConstraint]) + .DecorateMember(2) + .WithLeadingTrivia(xmlDoc); + + return method; + } + + 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 ArgumentListSyntax AddTargeter(ArgumentListSyntax invokerArguments, MethodDeclarationSyntax targetterMethod) + => SyntaxFactory.ArgumentList([SyntaxFactory.Argument(SyntaxFactory.IdentifierName(targetterMethod.Identifier)), ..invokerArguments.Arguments]); + + private static bool IsTargeterMethod(MethodDeclarationSyntax method) + => method.Identifier.ToString() == "GetFilterringTarget"; + + private static IEnumerable GetConstructors(ClassDeclarationSyntax classDeclaration) + => classDeclaration.Members.OfType().Where(ctor => ctor.Modifiers.HasModifiers("public")); + + private static MethodDeclarationSyntax FindTargetterMethod(Dictionary targeters, ClassDeclarationSyntax classDeclaration) + { + if (targeters.TryGetValue(classDeclaration.Identifier.ValueText, out MethodDeclarationSyntax targeter)) + return targeter; + + if (classDeclaration.BaseList != null && targeters.TryGetValue(classDeclaration.BaseList.Types.ElementAt(0).Type.ToString(), out targeter)) + return targeter; + + throw new TargteterNotFoundException(); + } + + private static SyntaxTriviaList BuildExtensionXmlDocTrivia(ClassDeclarationSyntax classDeclaration, ParameterListSyntax methodParameters) + { + StringBuilder summaryBuilder = new StringBuilder(); + + summaryBuilder + .Append("\t\t/// \n") + .Append("\t\t/// Adds a ").Append(classDeclaration.Identifier.ToString()).Append(" target filter to the handler builder.\n") + .Append("\t\t/// \n"); + + summaryBuilder + .AppendLine("\t\t/// The builder type.") + .AppendLine("\t\t/// The handler builder."); + + foreach (ParameterSyntax param in methodParameters.Parameters) + { + string name = param.Identifier.ToString(); + summaryBuilder + .Append("\t\t/// ") + .Append("The ").Append(name) + .AppendLine("."); + } + + summaryBuilder.AppendLine("\t\t/// The same builder instance."); + summaryBuilder.Append("\t\t"); + return SyntaxFactory.ParseLeadingTrivia(summaryBuilder.ToString()); + } + + private class UsingDirectiveEqualityComparer : IEqualityComparer + { + public bool Equals(UsingDirectiveSyntax x, UsingDirectiveSyntax y) + { + return x.ToString() == y.ToString(); + } + + public int GetHashCode(UsingDirectiveSyntax obj) + { + return obj.GetHashCode(); + } + } + + /* private static void ParseClassDeclaration(StringBuilder sourceBuilder, ClassDeclarationSyntax classDeclaration, Dictionary targeters) { string className = classDeclaration.Identifier.ToString(); @@ -130,7 +337,7 @@ namespace Telegrator.RoslynGenerators classTargetterMethodName = targeters[baseClassName]; } - if (classDeclaration.Modifiers.Any(keyword => keyword.ValueText == "abstract")) + if (classDeclaration.Modifiers.HasModifiers("abstract")) return; if (classDeclaration.ParameterList != null) @@ -157,7 +364,9 @@ namespace Telegrator.RoslynGenerators sourceBuilder.AppendLine(); } } + */ + /* private static void RenderExtensionMethod(StringBuilder sourceBuilder, string filterName, string classTargetterMethodName, SeparatedSyntaxList parameters, SeparatedSyntaxList arguments) { if (filterName == "ChatType") @@ -205,5 +414,6 @@ namespace Telegrator.RoslynGenerators sourceBuilder.Append(targeterMethod.Body.ToFullString()); } } + */ } } diff --git a/dev/Telegrator.RoslynGenerators/RoslynExtensions/CollectionsExtensions.cs b/dev/Telegrator.RoslynGenerators/RoslynExtensions/CollectionsExtensions.cs new file mode 100644 index 0000000..35bf4fe --- /dev/null +++ b/dev/Telegrator.RoslynGenerators/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, IEqualityComparer comparer) + { + foreach (TValue toUnionValue in toUnion) + { + if (!source.Contains(toUnionValue, comparer)) + 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).Select(_ => item); + } +} diff --git a/dev/Telegrator.RoslynExtensions/DiagnosticsHelper.cs b/dev/Telegrator.RoslynGenerators/RoslynExtensions/DiagnosticsHelper.cs similarity index 100% rename from dev/Telegrator.RoslynExtensions/DiagnosticsHelper.cs rename to dev/Telegrator.RoslynGenerators/RoslynExtensions/DiagnosticsHelper.cs diff --git a/dev/Telegrator.RoslynExtensions/Exceptions.cs b/dev/Telegrator.RoslynGenerators/RoslynExtensions/Exceptions.cs similarity index 100% rename from dev/Telegrator.RoslynExtensions/Exceptions.cs rename to dev/Telegrator.RoslynGenerators/RoslynExtensions/Exceptions.cs diff --git a/dev/Telegrator.RoslynGenerators/RoslynExtensions/MemberDeclarationSyntaxExtensions.cs b/dev/Telegrator.RoslynGenerators/RoslynExtensions/MemberDeclarationSyntaxExtensions.cs new file mode 100644 index 0000000..6e39050 --- /dev/null +++ b/dev/Telegrator.RoslynGenerators/RoslynExtensions/MemberDeclarationSyntaxExtensions.cs @@ -0,0 +1,39 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Telegrator.RoslynExtensions; + +namespace Telegrator.RoslynGenerators.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"); + + public static SyntaxTokenList Decorate(this SyntaxTokenList tokens) + => new SyntaxTokenList(tokens.Select(token => token.WithoutTrivia().WithTrailingTrivia(WhitespaceTrivia)).ToArray()); + + public static BlockSyntax DecorateBlock(this BlockSyntax block, int times) => block + .WithStatements([.. block.Statements.Select(statement => statement.DecorateStatememnt(times + 1))]) + .WithOpenBraceToken(SyntaxFactory.Token(SyntaxKind.OpenBraceToken).WithLeadingTrivia(WhitespaceTrivia).WithTrailingTrivia(NewLineTrivia)) + .WithCloseBraceToken(SyntaxFactory.Token(SyntaxKind.CloseBraceToken).WithLeadingTrivia(TabulationTrivia.Repeat(times)).WithTrailingTrivia(NewLineTrivia)); + + public static T DecorateStatememnt(this T statememnt, int times) where T : StatementSyntax => statememnt + .WithoutTrivia().WithLeadingTrivia(TabulationTrivia.Repeat(times)).WithTrailingTrivia(NewLineTrivia); + + public static T DecorateMember(this T typeDeclaration, int times) 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 DecorateType(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.RoslynGenerators/RoslynExtensions/StringBuilderExtensions.cs similarity index 100% rename from dev/Telegrator.RoslynExtensions/StringBuilderExtensions.cs rename to dev/Telegrator.RoslynGenerators/RoslynExtensions/StringBuilderExtensions.cs diff --git a/dev/Telegrator.RoslynExtensions/StringExtensions.cs b/dev/Telegrator.RoslynGenerators/RoslynExtensions/StringExtensions.cs similarity index 100% rename from dev/Telegrator.RoslynExtensions/StringExtensions.cs rename to dev/Telegrator.RoslynGenerators/RoslynExtensions/StringExtensions.cs diff --git a/dev/Telegrator.RoslynExtensions/SymbolsExtensions.cs b/dev/Telegrator.RoslynGenerators/RoslynExtensions/SymbolsExtensions.cs similarity index 100% rename from dev/Telegrator.RoslynExtensions/SymbolsExtensions.cs rename to dev/Telegrator.RoslynGenerators/RoslynExtensions/SymbolsExtensions.cs diff --git a/dev/Telegrator.RoslynExtensions/SyntaxNodesExtensions.cs b/dev/Telegrator.RoslynGenerators/RoslynExtensions/SyntaxNodesExtensions.cs similarity index 100% rename from dev/Telegrator.RoslynExtensions/SyntaxNodesExtensions.cs rename to dev/Telegrator.RoslynGenerators/RoslynExtensions/SyntaxNodesExtensions.cs diff --git a/dev/Telegrator.RoslynExtensions/SyntaxTokenExtensions.cs b/dev/Telegrator.RoslynGenerators/RoslynExtensions/SyntaxTokenExtensions.cs similarity index 100% rename from dev/Telegrator.RoslynExtensions/SyntaxTokenExtensions.cs rename to dev/Telegrator.RoslynGenerators/RoslynExtensions/SyntaxTokenExtensions.cs diff --git a/dev/Telegrator.RoslynGenerators/Telegrator.RoslynGenerators.csproj b/dev/Telegrator.RoslynGenerators/Telegrator.RoslynGenerators.csproj index 057ad41..f43357d 100644 --- a/dev/Telegrator.RoslynGenerators/Telegrator.RoslynGenerators.csproj +++ b/dev/Telegrator.RoslynGenerators/Telegrator.RoslynGenerators.csproj @@ -6,9 +6,6 @@ enable enable true - True - True - true @@ -19,10 +16,5 @@ - - - - - - +