< Summary

Line coverage
88%
Covered lines: 1676
Uncovered lines: 217
Coverable lines: 1893
Total lines: 3451
Line coverage: 88.5%
Branch coverage
69%
Covered branches: 754
Total branches: 1092
Branch coverage: 69%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
File 1: .cctor()100%11100%
File 1: Initialize(...)100%121295.65%
File 1: GetHandlerMethod(...)87.5%88100%
File 1: GetCallbackDataPayload(...)60%101090%
File 1: GetClassBasedHandlerMethod(...)58.33%131281.81%
File 1: GetErrorHandlerMethod(...)66.66%6683.33%
File 1: TryBuildHandler(...)59.09%444495%
File 1: TryBuildErrorHandler(...)46.66%303096.29%
File 1: TryBuildErrorExceptionTypes(...)70%111077.77%
File 1: TryBuildErrorParameters(...)65%222082.14%
File 1: TryBuildAutoAnswerCallback(...)85.71%141491.66%
File 1: BuildRoutes(...)76.72%13211689.33%
File 1: TryBuildFilters(...)50%4471.42%
File 1: AppendFilters(...)61.29%646291.74%
File 1: AppendChatTypeFilters(...)66.66%131281.25%
File 1: AppendSenderIdFilters(...)60%111081.25%
File 1: AppendMarkerFilter(...)100%22100%
File 1: SupportsGeneratedFilter(...)50%22100%
File 1: TryPrepareCustomFilters(...)83.33%6681.81%
File 1: TryResolveCustomFilterContextType(...)62.5%9871.42%
File 1: ToFullyQualifiedTypeName(...)100%11100%
File 1: CanonicalizeChatUsername(...)50%5466.66%
File 1: HasChatMemberTransitionAttributes(...)50%66100%
File 1: TryBuildRoleRequirements(...)66.66%7672.72%
File 1: TryBuildChatMemberTransitions(...)60%252077.27%
File 1: ToMetadataRouteKind(...)40%6562.5%
File 1: IsInvalidCustomFilterType(...)100%11100%
File 1: TryGetTelegramFilterContextTypes(...)64.28%1414100%
File 1: GetModuleName(...)100%88100%
File 1: TryGetExpectedContextType(...)100%44100%
File 1: HasExactlyOneExpectedContext(...)87.5%88100%
File 1: HasValidCallbackPayloadParameter(...)75%4483.33%
File 1: IsInvalidCallbackPayloadType(...)100%11100%
File 1: IsAccessibleFromGeneratedCode(...)62.5%9875%
File 1: TryGetCallbackDataFields(...)75%4490.9%
File 1: TryGetConstructorCallbackDataFields(...)60%101095%
File 1: TryGetPropertyCallbackDataFields(...)0%156120%
File 1: TryGetCallbackDataFieldKind(...)87.5%252488.23%
File 1: IsInvalidConcreteNamedType(...)50%1212100%
File 1: IsSupportedReturnType(...)100%22100%
File 1: TryGetErrorReturnKind(...)64.28%151485.71%
File 1: IsValidCommand(...)50%88100%
File 1: GetConstructorString(...)50%4475%
File 1: TryGetPrefixes(...)78.57%141494.11%
File 1: GetCommandPrefixMode(...)100%66100%
File 1: HasInvalidNoPrefixConfiguration(...)100%22100%
File 1: IsSupportedCommandPrefixMode(...)50%22100%
File 1: HasNamedArgument(...)100%11100%
File 1: CommandPatternStartsWithPrefix(...)100%11100%
File 1: GetNamedBool(...)100%66100%
File 1: TryParseTemplateRouteValues(...)75%262486.2%
File 1: TryParseRegexRouteValues(...)83.33%7671.42%
File 1: ContainsBrace(...)100%22100%
File 1: TryResolveRouteValueNames(...)72.72%262280%
File 1: IsSupportedRouteValueParameterType(...)100%11100%
File 1: IsNullableRouteValueParameter(...)50%6683.33%
File 1: GetRouteValueParameterSpecialType(...)100%88100%
File 1: RouteConstraintMatchesParameter(...)75%4475%
File 1: GetRouteValueTypeName(...)100%2283.33%
File 1: BuildTextFilters(...)71.42%1414100%
File 1: GetRouteAttributes(...)100%44100%
File 1: GetGenericRouteAttributes(...)100%44100%
File 1: IsClassBasedHandlerType(...)100%44100%
File 1: IsClassBasedRouteCompatible(...)61.11%191884.61%
File 1: GetDeclaredHandleAsyncMethods(...)50%66100%
File 1: TryGetCallbackHandlerPayloadType(...)100%66100%
File 1: IsAssignableTo(...)100%66100%
File 1: IsDirectlyAssignableTo(...)50%22100%
File 1: TryBuildStates(...)88.88%1818100%
File 1: GetGenericStateAttributes(...)100%11100%
File 1: TryResolveTypedState(...)50%201882.35%
File 1: TryResolveSceneStep(...)66.66%121293.75%
File 1: TryGetStateContainerPrefix(...)100%22100%
File 1: TryGetStateGroupPrefix(...)100%11100%
File 1: TryGetScenePrefix(...)100%11100%
File 1: TryGetAttributeStringPrefix(...)100%88100%
File 1: GetStateSegment(...)100%88100%
File 1: IsUsableStateProperty(...)100%44100%
File 1: IsPartialStateProperty(...)100%11100%
File 1: ToCamelCase(...)50%22100%
File 1: BuildParameters(...)100%22100%
File 1: GetParameterKind(...)100%1212100%
File 1: BuildErrorParameters(...)100%22100%
File 1: GetErrorParameterKind(...)100%1010100%
File 1: IsExceptionType(...)50%22100%
File 1: IsTelegramContextType(...)50%22100%
File 1: IsAssignableFrom(...)50%7666.66%
File 1: IsErrorRouteValueParameterType(...)100%11100%
File 1: IsRouteValueParameterType(...)85%2020100%
File 2: GenerateSource(...)100%3232100%
File 2: AppendCallbackDataCodecRegistration(...)100%22100%
File 2: GenerateStateGroupsSource(...)90%101096%
File 2: AppendRegistration(...)100%2020100%
File 2: AppendAutoAnswerCallback(...)100%22100%
File 2: AppendFilter(...)100%88100%
File 2: AppendErrorRegistration(...)100%22100%
File 2: AppendCallbackDataCodec(...)70%101084.21%
File 2: GetCallbackDataPackExpression(...)83.33%6690%
File 2: GetCallbackDataUnpackExpression(...)66.66%6680%
File 2: AppendCallbackDataHelpers(...)100%11100%
File 2: AppendInvoker(...)100%44100%
File 2: AppendErrorInvoker(...)100%44100%
File 2: AppendErrorInvocationArguments(...)100%44100%
File 2: ToNullableLiteral(...)100%22100%
File 2: ToTypeOfNullable(...)100%22100%
File 2: ToLiteral(...)66.66%151271.42%
File 2: ToBoolLiteral(...)100%22100%
File 2: ToCommandPrefixModeLiteral(...)25%5457.14%
File 2: ToAttributeCreationExpression(...)66.66%121286.36%
File 2: ToAttributeArgumentExpression(...)22.22%25941.66%
File 2: ToPrimitiveLiteral(...)11.76%4703427.77%
File 2: ToCharLiteral(...)0%110100%
File 2: ToEnumLiteral(...)0%156120%
File 2: ToArrayLiteral(...)0%4260%
File 3: .ctor(...)100%11100%
File 3: .ctor(...)100%11100%
File 3: .ctor(...)100%11100%
File 3: .ctor(...)100%11100%
File 3: .ctor(...)100%11100%
File 3: .ctor(...)100%11100%
File 3: .ctor(...)100%11100%
File 3: .ctor(...)100%11100%
File 3: .ctor(...)100%11100%
File 3: .ctor(...)100%11100%
File 3: .ctor(...)100%11100%
File 3: .ctor(...)100%11100%
File 4: GetStateGroup(...)66.66%121296.15%
File 4: GetAccessibility(...)50%5466.66%

File(s)

/_/src/TeleFlow.Generators/TelegramHandlerSourceGenerator.cs

#LineLine coverage
 1using System.Collections.Immutable;
 2using System.Text;
 3using System.Text.RegularExpressions;
 4using Microsoft.CodeAnalysis;
 5using Microsoft.CodeAnalysis.CSharp.Syntax;
 6using Microsoft.CodeAnalysis.Text;
 7
 8namespace TeleFlow.Generators;
 9
 10[Generator]
 11public sealed partial class TelegramHandlerSourceGenerator : IIncrementalGenerator
 12{
 113    private static readonly SymbolDisplayFormat FullyQualifiedFormat = SymbolDisplayFormat.FullyQualifiedFormat;
 14
 15    public void Initialize(IncrementalGeneratorInitializationContext context)
 16    {
 2817        IncrementalValueProvider<ImmutableArray<GeneratedHandlerMethod?>> methodHandlers = context.SyntaxProvider
 2818            .CreateSyntaxProvider(
 211719                static (node, _) => node is MethodDeclarationSyntax,
 5220                static (syntaxContext, _) => GetHandlerMethod(syntaxContext))
 5221            .Where(static method => method is not null)
 2822            .Collect();
 23
 2824        IncrementalValueProvider<ImmutableArray<GeneratedHandlerMethod?>> classBasedHandlers = context.SyntaxProvider
 2825            .CreateSyntaxProvider(
 211726                static (node, _) => node is TypeDeclarationSyntax,
 5127                static (syntaxContext, _) => GetClassBasedHandlerMethod(syntaxContext))
 5128            .Where(static method => method is not null)
 2829            .Collect();
 30
 2831        IncrementalValueProvider<ImmutableArray<GeneratedErrorHandlerMethod?>> errorHandlers = context.SyntaxProvider
 2832            .CreateSyntaxProvider(
 211733                static (node, _) => node is MethodDeclarationSyntax,
 5234                static (syntaxContext, _) => GetErrorHandlerMethod(syntaxContext))
 5235            .Where(static method => method is not null)
 2836            .Collect();
 37
 2838        IncrementalValueProvider<ImmutableArray<GeneratedStateGroup?>> stateGroups = context.SyntaxProvider
 2839            .CreateSyntaxProvider(
 211740                static (node, _) => node is TypeDeclarationSyntax { AttributeLists.Count: > 0 },
 1941                static (syntaxContext, _) => GetStateGroup(syntaxContext))
 1942            .Where(static group => group is not null)
 2843            .Collect();
 44
 2845        IncrementalValueProvider<ImmutableArray<GeneratedCallbackDataPayload?>> callbackDataPayloads = context.SyntaxPro
 2846            .CreateSyntaxProvider(
 211747                static (node, _) => node is TypeDeclarationSyntax { AttributeLists.Count: > 0 },
 1948                static (syntaxContext, _) => GetCallbackDataPayload(syntaxContext))
 1949            .Where(static payload => payload is not null)
 2850            .Collect();
 51
 2852        context.RegisterSourceOutput(
 2853            methodHandlers.Combine(classBasedHandlers).Combine(errorHandlers).Combine(callbackDataPayloads),
 2854            static (sourceContext, collectedHandlers) =>
 2855        {
 2856            GeneratedHandlerMethod[] handlers = collectedHandlers.Left.Left.Left
 2857                .Concat(collectedHandlers.Left.Left.Right)
 3958                .Where(static handler => handler is not null)
 2859                .Cast<GeneratedHandlerMethod>()
 3960                .GroupBy(static handler => handler.SignatureKey, StringComparer.Ordinal)
 3961                .Select(static group => group.First())
 2562                .OrderBy(static handler => handler.HandlerTypeMetadataName, StringComparer.Ordinal)
 2563                .ThenBy(static handler => handler.SourcePath, StringComparer.Ordinal)
 2564                .ThenBy(static handler => handler.SourceSpanStart)
 2865                .ToArray();
 2866            GeneratedErrorHandlerMethod[] errors = collectedHandlers.Left.Right
 367                .Where(static handler => handler is not null)
 2868                .Cast<GeneratedErrorHandlerMethod>()
 369                .GroupBy(static handler => handler.SignatureKey, StringComparer.Ordinal)
 370                .Select(static group => group.First())
 271                .OrderBy(static handler => handler.HandlerTypeMetadataName, StringComparer.Ordinal)
 272                .ThenBy(static handler => handler.SourcePath, StringComparer.Ordinal)
 273                .ThenBy(static handler => handler.SourceSpanStart)
 2874                .ToArray();
 2875            GeneratedCallbackDataPayload[] callbackPayloads = collectedHandlers.Right
 276                .Where(static payload => payload is not null)
 2877                .Cast<GeneratedCallbackDataPayload>()
 278                .GroupBy(static payload => payload.TypeMetadataName, StringComparer.Ordinal)
 279                .Select(static group => group.First())
 080                .OrderBy(static payload => payload.TypeMetadataName, StringComparer.Ordinal)
 081                .ThenBy(static payload => payload.SourcePath, StringComparer.Ordinal)
 082                .ThenBy(static payload => payload.SourceSpanStart)
 2883                .ToArray();
 2884
 2885            if (handlers.Length == 0 && errors.Length == 0 && callbackPayloads.Length == 0)
 2886            {
 487                return;
 2888            }
 2889
 2490            sourceContext.AddSource(
 2491                "TeleFlow.Telegram.GeneratedHandlers.g.cs",
 2492                SourceText.From(GenerateSource(handlers, errors, callbackPayloads), Encoding.UTF8));
 5293        });
 94
 2895        context.RegisterSourceOutput(stateGroups, static (sourceContext, collectedGroups) =>
 2896        {
 2897            GeneratedStateGroup[] groups = collectedGroups
 498                .Where(static group => group is not null)
 2899                .Cast<GeneratedStateGroup>()
 4100                .GroupBy(static group => group.TypeMetadataName, StringComparer.Ordinal)
 4101                .Select(static group => group.First())
 0102                .OrderBy(static group => group.TypeMetadataName, StringComparer.Ordinal)
 28103                .ToArray();
 28104
 28105            if (groups.Length == 0)
 28106            {
 24107                return;
 28108            }
 28109
 4110            sourceContext.AddSource(
 4111                "TeleFlow.StateGroups.g.cs",
 4112                SourceText.From(GenerateStateGroupsSource(groups), Encoding.UTF8));
 32113        });
 28114    }
 115
 116    private static GeneratedHandlerMethod? GetHandlerMethod(GeneratorSyntaxContext context)
 117    {
 52118        if (context.SemanticModel.GetDeclaredSymbol(context.Node) is not IMethodSymbol method ||
 52119            IsClassBasedHandlerType(method.ContainingType) ||
 52120            !TelegramHandlerSymbols.HasAnyRouteAttribute(method))
 121        {
 11122            return null;
 123        }
 124
 41125        if (!TryBuildHandler(method, includeClassRouteAttributes: false, out GeneratedHandlerMethod handler))
 126        {
 6127            return null;
 128        }
 129
 35130        return handler;
 131    }
 132
 133    private static GeneratedCallbackDataPayload? GetCallbackDataPayload(GeneratorSyntaxContext context)
 134    {
 19135        if (context.SemanticModel.GetDeclaredSymbol(context.Node) is not INamedTypeSymbol type)
 136        {
 0137            return null;
 138        }
 139
 19140        AttributeData? attribute = TelegramHandlerSymbols
 19141            .GetAttributes(type, TelegramHandlerSymbols.CallbackDataAttribute, inherit: false)
 19142            .FirstOrDefault();
 143
 19144        if (attribute is null)
 145        {
 17146            return null;
 147        }
 148
 2149        string? prefix = GetConstructorString(attribute);
 150
 2151        if (!TelegramCallbackDataFacts.IsValidPayloadPrefix(prefix) ||
 2152            !IsAccessibleFromGeneratedCode(type) ||
 2153            !TryGetCallbackDataFields(type, out bool usesConstructor, out ImmutableArray<GeneratedCallbackDataField> fie
 154        {
 0155            return null;
 156        }
 157
 2158        return new GeneratedCallbackDataPayload(
 2159            type.ToDisplayString(FullyQualifiedFormat),
 2160            type.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat),
 2161            prefix!,
 2162            usesConstructor,
 2163            fields,
 2164            context.Node.SyntaxTree.FilePath,
 2165            context.Node.SpanStart);
 166    }
 167
 168    private static GeneratedHandlerMethod? GetClassBasedHandlerMethod(GeneratorSyntaxContext context)
 169    {
 51170        if (context.SemanticModel.GetDeclaredSymbol(context.Node) is not INamedTypeSymbol type ||
 51171            !IsClassBasedHandlerType(type))
 172        {
 47173            return null;
 174        }
 175
 4176        ImmutableArray<IMethodSymbol> handleMethods = GetDeclaredHandleAsyncMethods(type);
 177
 4178        if (handleMethods.Length != 1 ||
 4179            (!TelegramHandlerSymbols.HasAnyRouteAttribute(type) &&
 4180             !TelegramHandlerSymbols.HasAnyRouteAttribute(handleMethods[0])))
 181        {
 0182            return null;
 183        }
 184
 4185        if (!TryBuildHandler(handleMethods[0], includeClassRouteAttributes: true, out GeneratedHandlerMethod handler))
 186        {
 0187            return null;
 188        }
 189
 4190        return handler;
 191    }
 192
 193    private static GeneratedErrorHandlerMethod? GetErrorHandlerMethod(GeneratorSyntaxContext context)
 194    {
 52195        if (context.SemanticModel.GetDeclaredSymbol(context.Node) is not IMethodSymbol method ||
 52196            !TelegramHandlerSymbols.HasAnyErrorAttribute(method))
 197        {
 49198            return null;
 199        }
 200
 3201        if (!TryBuildErrorHandler(method, out GeneratedErrorHandlerMethod handler))
 202        {
 0203            return null;
 204        }
 205
 3206        return handler;
 207    }
 208
 209    private static bool TryBuildHandler(
 210        IMethodSymbol method,
 211        bool includeClassRouteAttributes,
 212        out GeneratedHandlerMethod handler)
 213    {
 45214        handler = null!;
 215
 45216        ImmutableArray<GeneratedRoute> routes = BuildRoutes(method, includeClassRouteAttributes, out ITypeSymbol? callba
 217
 45218        if (routes.Length == 0 ||
 45219            method.DeclaredAccessibility != Accessibility.Public ||
 45220            method.IsStatic ||
 45221            method.IsGenericMethod ||
 45222            method.ContainingType.TypeKind is TypeKind.Interface ||
 45223            method.ContainingType.IsAbstract ||
 45224            !IsClassBasedRouteCompatible(method.ContainingType, routes[0].Kind, callbackPayloadType) ||
 45225            !IsSupportedReturnType(method.ReturnType) ||
 45226            !TryGetExpectedContextType(routes[0].Kind, out string expectedContextType) ||
 45227            !HasExactlyOneExpectedContext(method, expectedContextType) ||
 45228            !HasValidCallbackPayloadParameter(method, callbackPayloadType) ||
 45229            !TryResolveRouteValueNames(method, routes, out ImmutableHashSet<string> routeValueNames))
 230        {
 5231            return false;
 232        }
 233
 40234        if (!TryBuildStates(method, out ImmutableArray<string> states, out string? sceneName))
 235        {
 1236            return false;
 237        }
 238
 39239        MethodDeclarationSyntax? syntax = method.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax() as MethodDeclar
 78240        Location? location = method.Locations.FirstOrDefault(location => location.IsInSource);
 39241        ImmutableArray<GeneratedParameter> parameters = BuildParameters(method, routes[0].Kind, expectedContextType, cal
 80242        bool hasCallbackRoute = routes.Any(static route => route.Kind == GeneratedHandlerKind.Callback);
 243
 39244        if (parameters.Length == 0)
 245        {
 0246            return false;
 247        }
 248
 39249        if (!TryBuildAutoAnswerCallback(method, hasCallbackRoute, out GeneratedAutoAnswerCallback? autoAnswerCallback))
 250        {
 0251            return false;
 252        }
 253
 39254        handler = new GeneratedHandlerMethod(
 39255            SignatureKey: method.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat),
 39256            HandlerTypeName: method.ContainingType.ToDisplayString(FullyQualifiedFormat),
 39257            HandlerTypeMetadataName: method.ContainingType.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)
 39258            MethodName: method.Name,
 39259            ModuleName: GetModuleName(method.ContainingType),
 39260            SceneName: sceneName,
 39261            CallbackPayloadType: callbackPayloadType?.ToDisplayString(FullyQualifiedFormat),
 39262            AutoAnswerCallback: autoAnswerCallback,
 39263            Routes: routes,
 39264            States: states,
 39265            Parameters: parameters,
 39266            SourcePath: location?.SourceTree?.FilePath ?? string.Empty,
 39267            SourceSpanStart: location?.SourceSpan.Start ?? syntax?.SpanStart ?? 0);
 268
 39269        return true;
 270    }
 271
 272    private static bool TryBuildErrorHandler(
 273        IMethodSymbol method,
 274        out GeneratedErrorHandlerMethod handler)
 275    {
 3276        handler = null!;
 277
 3278        IReadOnlyList<AttributeData> errorAttributes = TelegramHandlerSymbols.GetErrorAttributes(method, inherit: true);
 279
 3280        if (errorAttributes.Count == 0 ||
 3281            method.DeclaredAccessibility != Accessibility.Public ||
 3282            method.IsStatic ||
 3283            method.IsGenericMethod ||
 3284            method.ContainingType.TypeKind is TypeKind.Interface ||
 3285            method.ContainingType.IsAbstract ||
 3286            !TryGetErrorReturnKind(method.ReturnType, out GeneratedErrorReturnKind returnKind) ||
 3287            !TryBuildErrorExceptionTypes(errorAttributes, out ImmutableArray<ITypeSymbol?> exceptionTypes, out Immutable
 3288            !TryBuildErrorParameters(method, exceptionTypes, out ImmutableArray<GeneratedErrorParameter> parameters, out
 289        {
 0290            return false;
 291        }
 292
 3293        MethodDeclarationSyntax? syntax = method.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax() as MethodDeclar
 6294        Location? location = method.Locations.FirstOrDefault(static location => location.IsInSource);
 295
 3296        handler = new GeneratedErrorHandlerMethod(
 3297            SignatureKey: method.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat),
 3298            HandlerTypeName: method.ContainingType.ToDisplayString(FullyQualifiedFormat),
 3299            HandlerTypeMetadataName: method.ContainingType.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)
 3300            MethodName: method.Name,
 3301            ModuleName: GetModuleName(method.ContainingType),
 3302            ExceptionTypes: exceptionTypeNames,
 3303            TelegramContextType: telegramContextType,
 3304            ReturnKind: returnKind,
 3305            Parameters: parameters,
 3306            SourcePath: location?.SourceTree?.FilePath ?? string.Empty,
 3307            SourceSpanStart: location?.SourceSpan.Start ?? syntax?.SpanStart ?? 0);
 308
 3309        return true;
 310    }
 311
 312    private static bool TryBuildErrorExceptionTypes(
 313        IReadOnlyList<AttributeData> attributes,
 314        out ImmutableArray<ITypeSymbol?> exceptionTypes,
 315        out ImmutableArray<string?> exceptionTypeNames)
 316    {
 3317        ImmutableArray<ITypeSymbol?>.Builder symbols = ImmutableArray.CreateBuilder<ITypeSymbol?>();
 3318        ImmutableArray<string?>.Builder names = ImmutableArray.CreateBuilder<string?>();
 319
 12320        foreach (AttributeData attribute in attributes)
 321        {
 3322            if (TelegramHandlerSymbols.IsGenericAttribute(attribute, TelegramHandlerSymbols.GenericErrorAttribute))
 323            {
 2324                if (attribute.AttributeClass is not { TypeArguments.Length: 1 } genericAttribute ||
 2325                    !IsExceptionType(genericAttribute.TypeArguments[0]))
 326                {
 0327                    exceptionTypes = [];
 0328                    exceptionTypeNames = [];
 0329                    return false;
 330                }
 331
 2332                symbols.Add(genericAttribute.TypeArguments[0]);
 2333                names.Add(genericAttribute.TypeArguments[0].ToDisplayString(FullyQualifiedFormat));
 2334                continue;
 335            }
 336
 1337            symbols.Add(null);
 1338            names.Add(null);
 339        }
 340
 3341        exceptionTypes = symbols.ToImmutable();
 3342        exceptionTypeNames = names.ToImmutable();
 3343        return exceptionTypes.Length > 0;
 0344    }
 345
 346    private static bool TryBuildErrorParameters(
 347        IMethodSymbol method,
 348        ImmutableArray<ITypeSymbol?> exceptionTypes,
 349        out ImmutableArray<GeneratedErrorParameter> parameters,
 350        out string? telegramContextType)
 351    {
 3352        parameters = [];
 3353        telegramContextType = null;
 354
 3355        if (method.Parameters.Count(static parameter =>
 11356                TelegramHandlerSymbols.IsType(parameter.Type, TelegramHandlerSymbols.TelegramErrorContext)) > 1 ||
 3357            method.Parameters.Count(static parameter =>
 14358                TelegramHandlerSymbols.IsType(parameter.Type, TelegramHandlerSymbols.CancellationToken)) > 1)
 359        {
 0360            return false;
 361        }
 362
 3363        IParameterSymbol[] exceptionParameters = method.Parameters
 11364            .Where(static parameter => IsExceptionType(parameter.Type))
 3365            .ToArray();
 366
 3367        if (exceptionParameters.Length > 1)
 368        {
 0369            return false;
 370        }
 371
 3372        IParameterSymbol[] telegramContextParameters = method.Parameters
 11373            .Where(static parameter => IsTelegramContextType(parameter.Type))
 3374            .ToArray();
 375
 3376        if (telegramContextParameters.Length > 1)
 377        {
 0378            return false;
 379        }
 380
 3381        IParameterSymbol? exceptionParameter = exceptionParameters.FirstOrDefault();
 382
 3383        if (exceptionParameter is not null)
 384        {
 12385            foreach (ITypeSymbol? exceptionType in exceptionTypes)
 386            {
 3387                if (exceptionType is null)
 388                {
 1389                    if (!TelegramHandlerSymbols.IsType(exceptionParameter.Type, TelegramHandlerSymbols.Exception))
 390                    {
 0391                        return false;
 392                    }
 393
 394                    continue;
 395                }
 396
 2397                if (!IsAssignableFrom(exceptionParameter.Type, exceptionType))
 398                {
 0399                    return false;
 400                }
 401            }
 402        }
 403
 3404        telegramContextType = telegramContextParameters.FirstOrDefault()?.Type.ToDisplayString(FullyQualifiedFormat);
 3405        parameters = BuildErrorParameters(method);
 3406        return true;
 407    }
 408
 409    private static bool TryBuildAutoAnswerCallback(
 410        IMethodSymbol method,
 411        bool hasCallbackRoute,
 412        out GeneratedAutoAnswerCallback? autoAnswerCallback)
 413    {
 39414        autoAnswerCallback = null;
 415
 39416        AttributeData? methodAttribute = TelegramHandlerSymbols.GetFirstAttribute(
 39417            method,
 39418            TelegramHandlerSymbols.AutoAnswerCallbackAttribute,
 39419            inherit: true);
 420
 39421        if (methodAttribute is not null && !hasCallbackRoute)
 422        {
 0423            return false;
 424        }
 425
 39426        if (!hasCallbackRoute)
 427        {
 33428            return true;
 429        }
 430
 6431        AttributeData? attribute = methodAttribute ??
 6432            TelegramHandlerSymbols.GetFirstAttribute(
 6433                method.ContainingType,
 6434                TelegramHandlerSymbols.AutoAnswerCallbackAttribute,
 6435                inherit: true);
 436
 6437        if (attribute is null)
 438        {
 5439            return true;
 440        }
 441
 1442        string? text = GetConstructorString(attribute);
 1443        if (text is not null && string.IsNullOrWhiteSpace(text))
 444        {
 0445            return false;
 446        }
 447
 1448        autoAnswerCallback = new GeneratedAutoAnswerCallback(
 1449            GetNamedBool(attribute, "Enabled", defaultValue: true),
 1450            text,
 1451            GetNamedBool(attribute, "ShowAlert", defaultValue: false));
 1452        return true;
 453    }
 454
 455    private static ImmutableArray<GeneratedRoute> BuildRoutes(
 456        IMethodSymbol method,
 457        bool includeClassRouteAttributes,
 458        out ITypeSymbol? callbackPayloadType)
 459    {
 45460        callbackPayloadType = null;
 45461        bool hasRawCallback = GetRouteAttributes(
 45462            method,
 45463            TelegramHandlerSymbols.CallbackAttribute,
 45464            includeClassRouteAttributes)
 45465            .Count > 0;
 45466        IReadOnlyList<AttributeData> genericCallbackAttributes = GetGenericRouteAttributes(
 45467            method,
 45468            TelegramHandlerSymbols.GenericCallbackAttribute,
 45469            includeClassRouteAttributes);
 45470        AttributeData? genericCallback = genericCallbackAttributes.Count > 0
 45471            ? genericCallbackAttributes[0]
 45472            : null;
 473
 45474        if (genericCallback?.AttributeClass is { TypeArguments.Length: 1 } genericCallbackType)
 475        {
 3476            callbackPayloadType = genericCallbackType.TypeArguments[0];
 477        }
 478
 45479        int messageRouteAttributeCount =
 45480            GetRouteAttributes(method, TelegramHandlerSymbols.CommandAttribute, includeClassRouteAttributes).Count +
 45481            GetRouteAttributes(method, TelegramHandlerSymbols.MessageAttribute, includeClassRouteAttributes).Count +
 45482            GetRouteAttributes(method, TelegramHandlerSymbols.TextAttribute, includeClassRouteAttributes).Count +
 45483            GetRouteAttributes(method, TelegramHandlerSymbols.TextTemplateAttribute, includeClassRouteAttributes).Count 
 45484            GetRouteAttributes(method, TelegramHandlerSymbols.CommandTemplateAttribute, includeClassRouteAttributes).Cou
 45485            GetRouteAttributes(method, TelegramHandlerSymbols.TextRegexAttribute, includeClassRouteAttributes).Count +
 45486            GetRouteAttributes(method, TelegramHandlerSymbols.CommandRegexAttribute, includeClassRouteAttributes).Count;
 45487        bool hasChatMemberUpdated = GetRouteAttributes(
 45488            method,
 45489            TelegramHandlerSymbols.ChatMemberUpdatedAttribute,
 45490            includeClassRouteAttributes).Count > 0;
 45491        bool hasMyChatMemberUpdated = GetRouteAttributes(
 45492            method,
 45493            TelegramHandlerSymbols.MyChatMemberUpdatedAttribute,
 45494            includeClassRouteAttributes).Count > 0;
 45495        bool hasChatMemberRoute = hasChatMemberUpdated || hasMyChatMemberUpdated;
 496
 45497        if ((hasRawCallback || callbackPayloadType is not null) &&
 45498            (messageRouteAttributeCount > 0 || hasChatMemberRoute || (hasRawCallback && callbackPayloadType is not null)
 499        {
 0500            return [];
 501        }
 502
 45503        if (hasChatMemberRoute && messageRouteAttributeCount > 0)
 504        {
 0505            return [];
 506        }
 507
 45508        if (!hasChatMemberRoute && HasChatMemberTransitionAttributes(method))
 509        {
 0510            return [];
 511        }
 512
 45513        if (!TryBuildFilters(method, out ImmutableArray<GeneratedFilter> filters))
 514        {
 0515            return [];
 516        }
 517
 45518        if (!TryBuildRoleRequirements(method, out ImmutableArray<int> roleRequirements))
 519        {
 0520            return [];
 521        }
 522
 45523        if (hasChatMemberRoute)
 524        {
 7525            if (!TryPrepareCustomFilters(filters, GeneratedHandlerKind.ChatMember, out ImmutableArray<GeneratedFilter> c
 0526                chatMemberFilters.Any(static filter => filter.CustomTypeName is null &&
 0527                                             !SupportsGeneratedFilter(filter, GeneratedHandlerKind.ChatMember)) ||
 7528                !TryBuildChatMemberTransitions(method, out ImmutableArray<GeneratedChatMemberTransition> transitions))
 529            {
 0530                return [];
 531            }
 532
 7533            filters = chatMemberFilters;
 7534            ImmutableArray<GeneratedRoute>.Builder chatMemberRoutes = ImmutableArray.CreateBuilder<GeneratedRoute>();
 535
 7536            if (hasChatMemberUpdated)
 537            {
 6538                chatMemberRoutes.Add(new GeneratedRoute(
 6539                    GeneratedHandlerKind.ChatMember,
 6540                    GeneratedRouteKind.ChatMemberUpdated,
 6541                    Command: null,
 6542                    Pattern: null,
 6543                    CommandPrefixes: ["/"],
 6544                    AllowSpaceAfterPrefix: false,
 6545                    PrefixMode: CommandPrefixModeRequired,
 6546                    IgnoreCase: true,
 6547                    TextFilters: [],
 6548                    filters,
 6549                    transitions,
 6550                    roleRequirements,
 6551                    RouteValues: ImmutableDictionary<string, GeneratedRouteValue>.Empty));
 552            }
 553
 7554            if (hasMyChatMemberUpdated)
 555            {
 1556                chatMemberRoutes.Add(new GeneratedRoute(
 1557                    GeneratedHandlerKind.ChatMember,
 1558                    GeneratedRouteKind.MyChatMemberUpdated,
 1559                    Command: null,
 1560                    Pattern: null,
 1561                    CommandPrefixes: ["/"],
 1562                    AllowSpaceAfterPrefix: false,
 1563                    PrefixMode: CommandPrefixModeRequired,
 1564                    IgnoreCase: true,
 1565                    TextFilters: [],
 1566                    filters,
 1567                    transitions,
 1568                    roleRequirements,
 1569                    RouteValues: ImmutableDictionary<string, GeneratedRouteValue>.Empty));
 570            }
 571
 7572            return chatMemberRoutes.ToImmutable();
 573        }
 574
 38575        if (hasRawCallback || callbackPayloadType is not null)
 576        {
 6577            if (!TryPrepareCustomFilters(filters, GeneratedHandlerKind.Callback, out ImmutableArray<GeneratedFilter> cal
 10578                callbackFilters.Any(static filter => filter.CustomTypeName is null &&
 10579                                             !SupportsGeneratedFilter(filter, GeneratedHandlerKind.Callback)))
 580            {
 0581                return [];
 582            }
 583
 6584            filters = callbackFilters;
 6585            return
 6586            [
 6587                new GeneratedRoute(
 6588                    GeneratedHandlerKind.Callback,
 6589                    GeneratedRouteKind.Callback,
 6590                    Command: null,
 6591                    Pattern: null,
 6592                    CommandPrefixes: ["/"],
 6593                    AllowSpaceAfterPrefix: false,
 6594                    PrefixMode: CommandPrefixModeRequired,
 6595                    IgnoreCase: true,
 6596                    TextFilters: [],
 6597                    filters,
 6598                    ChatMemberTransitions: [],
 6599                    RoleRequirements: roleRequirements,
 6600                    RouteValues: ImmutableDictionary<string, GeneratedRouteValue>.Empty)
 6601            ];
 602        }
 603
 32604        if (!TryPrepareCustomFilters(filters, GeneratedHandlerKind.Message, out ImmutableArray<GeneratedFilter> messageF
 54605            messageFilters.Any(static filter => filter.CustomTypeName is null &&
 54606                                         !SupportsGeneratedFilter(filter, GeneratedHandlerKind.Message)))
 607        {
 0608            return [];
 609        }
 610
 32611        filters = messageFilters;
 32612        ImmutableArray<GeneratedRoute>.Builder routes = ImmutableArray.CreateBuilder<GeneratedRoute>();
 613
 87614        foreach (AttributeData attribute in GetRouteAttributes(
 32615                     method,
 32616                     TelegramHandlerSymbols.CommandAttribute,
 32617                     includeClassRouteAttributes))
 618        {
 12619            string? command = GetConstructorString(attribute);
 12620            int prefixMode = GetCommandPrefixMode(attribute);
 621
 12622            if (!IsValidCommand(command) ||
 12623                !TryGetPrefixes(attribute, out ImmutableArray<string> prefixes) ||
 12624                !IsSupportedCommandPrefixMode(prefixMode) ||
 12625                HasInvalidNoPrefixConfiguration(attribute, prefixMode))
 626            {
 1627                return [];
 628            }
 629
 11630            routes.Add(new GeneratedRoute(
 11631                GeneratedHandlerKind.Command,
 11632                GeneratedRouteKind.CommandExact,
 11633                command,
 11634                command,
 11635                prefixes,
 11636                GetNamedBool(attribute, "AllowSpaceAfterPrefix", defaultValue: false),
 11637                prefixMode,
 11638                GetNamedBool(attribute, "IgnoreCase", defaultValue: true),
 11639                TextFilters: [],
 11640                filters,
 11641                ChatMemberTransitions: [],
 11642                RoleRequirements: roleRequirements,
 11643                RouteValues: ImmutableDictionary<string, GeneratedRouteValue>.Empty));
 644        }
 645
 66646        foreach (AttributeData attribute in GetRouteAttributes(
 31647                     method,
 31648                     TelegramHandlerSymbols.CommandTemplateAttribute,
 31649                     includeClassRouteAttributes))
 650        {
 3651            string? template = GetConstructorString(attribute);
 3652            int prefixMode = GetCommandPrefixMode(attribute);
 653
 3654            if (template is null ||
 3655                string.IsNullOrWhiteSpace(template) ||
 3656                !TryParseTemplateRouteValues(template, out ImmutableDictionary<string, GeneratedRouteValue> routeValues)
 3657                !TryGetPrefixes(attribute, out ImmutableArray<string> prefixes) ||
 3658                !IsSupportedCommandPrefixMode(prefixMode) ||
 3659                HasInvalidNoPrefixConfiguration(attribute, prefixMode) ||
 3660                CommandPatternStartsWithPrefix(template, prefixes))
 661            {
 2662                return [];
 663            }
 664
 1665            routes.Add(new GeneratedRoute(
 1666                GeneratedHandlerKind.Command,
 1667                GeneratedRouteKind.CommandTemplate,
 1668                Command: null,
 1669                Pattern: template.Trim(),
 1670                prefixes,
 1671                GetNamedBool(attribute, "AllowSpaceAfterPrefix", defaultValue: false),
 1672                prefixMode,
 1673                GetNamedBool(attribute, "IgnoreCase", defaultValue: true),
 1674                TextFilters: [],
 1675                filters,
 1676                ChatMemberTransitions: [],
 1677                RoleRequirements: roleRequirements,
 1678                routeValues));
 679        }
 680
 60681        foreach (AttributeData attribute in GetRouteAttributes(
 29682                     method,
 29683                     TelegramHandlerSymbols.CommandRegexAttribute,
 29684                     includeClassRouteAttributes))
 685        {
 1686            string? pattern = GetConstructorString(attribute);
 1687            int prefixMode = GetCommandPrefixMode(attribute);
 688
 1689            if (string.IsNullOrWhiteSpace(pattern) ||
 1690                !TryParseRegexRouteValues(pattern, out ImmutableDictionary<string, GeneratedRouteValue> routeValues) ||
 1691                !TryGetPrefixes(attribute, out ImmutableArray<string> prefixes) ||
 1692                !IsSupportedCommandPrefixMode(prefixMode) ||
 1693                HasInvalidNoPrefixConfiguration(attribute, prefixMode))
 694            {
 0695                return [];
 696            }
 697
 1698            routes.Add(new GeneratedRoute(
 1699                GeneratedHandlerKind.Command,
 1700                GeneratedRouteKind.CommandRegex,
 1701                Command: null,
 1702                Pattern: pattern,
 1703                prefixes,
 1704                GetNamedBool(attribute, "AllowSpaceAfterPrefix", defaultValue: false),
 1705                prefixMode,
 1706                GetNamedBool(attribute, "IgnoreCase", defaultValue: true),
 1707                TextFilters: [],
 1708                filters,
 1709                ChatMemberTransitions: [],
 1710                RoleRequirements: roleRequirements,
 1711                routeValues));
 712        }
 713
 29714        ImmutableArray<GeneratedTextFilter> textFilters = BuildTextFilters(method, includeClassRouteAttributes);
 29715        bool hasMessageAttribute = GetRouteAttributes(
 29716            method,
 29717            TelegramHandlerSymbols.MessageAttribute,
 29718            includeClassRouteAttributes).Count > 0;
 719
 29720        if (hasMessageAttribute)
 721        {
 13722            routes.Add(new GeneratedRoute(
 13723                GeneratedHandlerKind.Message,
 13724                GeneratedRouteKind.MessageAny,
 13725                Command: null,
 13726                Pattern: null,
 13727                CommandPrefixes: ["/"],
 13728                AllowSpaceAfterPrefix: false,
 13729                PrefixMode: CommandPrefixModeRequired,
 13730                IgnoreCase: true,
 13731                textFilters,
 13732                filters,
 13733                ChatMemberTransitions: [],
 13734                RoleRequirements: roleRequirements,
 13735                RouteValues: ImmutableDictionary<string, GeneratedRouteValue>.Empty));
 736        }
 737        else
 738        {
 34739            foreach (GeneratedTextFilter filter in textFilters)
 740            {
 1741                routes.Add(new GeneratedRoute(
 1742                    GeneratedHandlerKind.Message,
 1743                    GeneratedRouteKind.TextExact,
 1744                    Command: null,
 1745                    Pattern: filter.Value,
 1746                    CommandPrefixes: ["/"],
 1747                    AllowSpaceAfterPrefix: false,
 1748                    PrefixMode: CommandPrefixModeRequired,
 1749                    IgnoreCase: filter.IgnoreCase,
 1750                    TextFilters: [filter],
 1751                    filters,
 1752                    ChatMemberTransitions: [],
 1753                    RoleRequirements: roleRequirements,
 1754                    RouteValues: ImmutableDictionary<string, GeneratedRouteValue>.Empty));
 755            }
 756        }
 757
 65758        foreach (AttributeData attribute in GetRouteAttributes(
 29759                     method,
 29760                     TelegramHandlerSymbols.TextTemplateAttribute,
 29761                     includeClassRouteAttributes))
 762        {
 4763            string? template = GetConstructorString(attribute);
 764
 4765            if (template is null ||
 4766                string.IsNullOrWhiteSpace(template) ||
 4767                !TryParseTemplateRouteValues(template, out ImmutableDictionary<string, GeneratedRouteValue> routeValues)
 768            {
 1769                return [];
 770            }
 771
 3772            routes.Add(new GeneratedRoute(
 3773                GeneratedHandlerKind.Message,
 3774                GeneratedRouteKind.TextTemplate,
 3775                Command: null,
 3776                Pattern: template.Trim(),
 3777                    CommandPrefixes: ["/"],
 3778                    AllowSpaceAfterPrefix: false,
 3779                    PrefixMode: CommandPrefixModeRequired,
 3780                    IgnoreCase: GetNamedBool(attribute, "IgnoreCase", defaultValue: true),
 3781                    TextFilters: [],
 3782                filters,
 3783                ChatMemberTransitions: [],
 3784                RoleRequirements: roleRequirements,
 3785                routeValues));
 786        }
 787
 56788        foreach (AttributeData attribute in GetRouteAttributes(
 28789                     method,
 28790                     TelegramHandlerSymbols.TextRegexAttribute,
 28791                     includeClassRouteAttributes))
 792        {
 0793            string? pattern = GetConstructorString(attribute);
 794
 0795            if (string.IsNullOrWhiteSpace(pattern) ||
 0796                !TryParseRegexRouteValues(pattern, out ImmutableDictionary<string, GeneratedRouteValue> routeValues))
 797            {
 0798                return [];
 799            }
 800
 0801            routes.Add(new GeneratedRoute(
 0802                GeneratedHandlerKind.Message,
 0803                GeneratedRouteKind.TextRegex,
 0804                Command: null,
 0805                Pattern: pattern,
 0806                    CommandPrefixes: ["/"],
 0807                    AllowSpaceAfterPrefix: false,
 0808                    PrefixMode: CommandPrefixModeRequired,
 0809                    IgnoreCase: GetNamedBool(attribute, "IgnoreCase", defaultValue: true),
 0810                    TextFilters: [],
 0811                filters,
 0812                ChatMemberTransitions: [],
 0813                RoleRequirements: roleRequirements,
 0814                routeValues));
 815        }
 816
 28817        return routes.ToImmutable();
 4818    }
 819
 820    private static bool TryBuildFilters(
 821        IMethodSymbol method,
 822        out ImmutableArray<GeneratedFilter> filters)
 823    {
 45824        ImmutableArray<GeneratedFilter>.Builder builder = ImmutableArray.CreateBuilder<GeneratedFilter>();
 825
 45826        if (!AppendFilters(builder, method.ContainingType) ||
 45827            !AppendFilters(builder, method))
 828        {
 0829            filters = [];
 0830            return false;
 831        }
 832
 45833        filters = builder.ToImmutable();
 45834        return true;
 835    }
 836
 837    private static bool AppendFilters(
 838        ImmutableArray<GeneratedFilter>.Builder builder,
 839        ISymbol symbol)
 840    {
 90841        if (!AppendChatTypeFilters(
 90842                builder,
 90843                symbol,
 90844                TelegramHandlerSymbols.ChatTypeAttribute,
 90845                "ChatType") ||
 90846            !AppendChatTypeFilters(
 90847                builder,
 90848                symbol,
 90849                TelegramHandlerSymbols.SenderChatTypeAttribute,
 90850                "SenderChatType"))
 851        {
 0852            return false;
 853        }
 854
 184855        foreach (AttributeData attribute in TelegramHandlerSymbols.GetAttributes(
 90856                     symbol,
 90857                     TelegramHandlerSymbols.ChatIdAttribute,
 90858                     inherit: true))
 859        {
 2860            ImmutableArray<long> values = attribute.ConstructorArguments.Length > 0
 2861                ? attribute.ConstructorArguments[0].Values
 2862                    .Select(static value => value.Value)
 2863                    .OfType<long>()
 2864                    .ToImmutableArray()
 2865                : [];
 866
 2867            if (values.Length == 0 ||
 4868                values.Any(static value => value == 0))
 869            {
 0870                return false;
 871            }
 872
 2873            builder.Add(new GeneratedFilter("ChatId", StringValues: [], values));
 874        }
 875
 182876        foreach (AttributeData attribute in TelegramHandlerSymbols.GetAttributes(
 90877                     symbol,
 90878                     TelegramHandlerSymbols.ChatUsernameAttribute,
 90879                     inherit: true))
 880        {
 1881            ImmutableArray<string> values = attribute.ConstructorArguments.Length > 0
 1882                ? attribute.ConstructorArguments[0].Values
 1883                    .Select(static value => value.Value as string)
 1884                    .Select(CanonicalizeChatUsername)
 1885                    .ToImmutableArray()
 1886                : [];
 887
 1888            if (values.Length == 0 ||
 2889                values.Any(static value => value.Length == 0))
 890            {
 0891                return false;
 892            }
 893
 1894            builder.Add(new GeneratedFilter("ChatUsername", values, LongValues: []));
 895        }
 896
 90897        if (!AppendSenderIdFilters(
 90898                builder,
 90899                symbol,
 90900                TelegramHandlerSymbols.FromUserAttribute,
 90901                "FromUser") ||
 90902            !AppendSenderIdFilters(
 90903                builder,
 90904                symbol,
 90905                TelegramHandlerSymbols.FromBotAttribute,
 90906                "FromBot"))
 907        {
 0908            return false;
 909        }
 910
 3780911        foreach (TelegramBuiltInFilterSpec spec in TelegramBuiltInFilterFacts.MarkerSpecs)
 912        {
 1800913            AppendMarkerFilter(builder, symbol, spec);
 914        }
 915
 182916        foreach (AttributeData attribute in TelegramHandlerSymbols.GetAttributes(
 90917                     symbol,
 90918                     TelegramHandlerSymbols.MessageThreadIdAttribute,
 90919                     inherit: true))
 920        {
 1921            ImmutableArray<long> values = attribute.ConstructorArguments.Length > 0
 1922                ? attribute.ConstructorArguments[0].Values
 1923                    .Select(static value => value.Value)
 1924                    .OfType<long>()
 1925                    .ToImmutableArray()
 1926                : [];
 927
 1928            if (values.Length == 0 ||
 2929                values.Any(static value => value <= 0))
 930            {
 0931                return false;
 932            }
 933
 1934            builder.Add(new GeneratedFilter("MessageThreadId", StringValues: [], values));
 935        }
 936
 184937        foreach (AttributeData attribute in TelegramHandlerSymbols.GetAttributes(
 90938                     symbol,
 90939                     TelegramHandlerSymbols.CallbackDataPrefixAttribute,
 90940                     inherit: true))
 941        {
 2942            if (GetConstructorString(attribute) is not { } prefix ||
 2943                string.IsNullOrWhiteSpace(prefix))
 944            {
 0945                return false;
 946            }
 947
 2948            builder.Add(new GeneratedFilter("CallbackDataPrefix", [prefix], LongValues: []));
 949        }
 950
 184951        foreach (AttributeData attribute in TelegramHandlerSymbols.GetGenericAttributes(
 90952                     symbol,
 90953                     TelegramHandlerSymbols.GenericUseFilterAttribute,
 90954                     inherit: true))
 955        {
 2956            if (attribute.AttributeClass is not { TypeArguments.Length: 1 } filterAttributeType ||
 2957                IsInvalidCustomFilterType(filterAttributeType.TypeArguments[0]) ||
 2958                !TryGetTelegramFilterContextTypes(
 2959                    filterAttributeType.TypeArguments[0],
 2960                    attributeType: null,
 2961                    out ImmutableArray<string> contextMetadataNames))
 962            {
 0963                return false;
 964            }
 965
 2966            builder.Add(new GeneratedFilter(
 2967                "Custom",
 2968                StringValues: [],
 2969                LongValues: [],
 2970                filterAttributeType.TypeArguments[0].ToDisplayString(FullyQualifiedFormat),
 2971                CustomContextMetadataNames: contextMetadataNames));
 972        }
 973
 182974        foreach (AttributeData attribute in TelegramHandlerSymbols.GetTelegramFilterAttributes(symbol, inherit: true))
 975        {
 1976            if (attribute.AttributeClass is not { IsGenericType: false } attributeType ||
 1977                !TelegramHandlerSymbols.TryGetTelegramFilterAttributeFilterType(attribute, out ITypeSymbol filterType) |
 1978                IsInvalidCustomFilterType(filterType) ||
 1979                !TryGetTelegramFilterContextTypes(
 1980                    filterType,
 1981                    attributeType,
 1982                    out ImmutableArray<string> contextMetadataNames))
 983            {
 0984                return false;
 985            }
 986
 1987            builder.Add(new GeneratedFilter(
 1988                "Custom",
 1989                StringValues: [],
 1990                LongValues: [],
 1991                filterType.ToDisplayString(FullyQualifiedFormat),
 1992                ToAttributeCreationExpression(attribute),
 1993                CustomContextMetadataNames: contextMetadataNames));
 994        }
 995
 90996        return true;
 0997    }
 998
 999    private static bool AppendChatTypeFilters(
 1000        ImmutableArray<GeneratedFilter>.Builder builder,
 1001        ISymbol symbol,
 1002        string attributeMetadataName,
 1003        string generatedKind)
 1004    {
 3681005        foreach (AttributeData attribute in TelegramHandlerSymbols.GetAttributes(
 1801006                     symbol,
 1801007                     attributeMetadataName,
 1801008                     inherit: true))
 1009        {
 41010            if (attribute.ConstructorArguments.Length == 0 ||
 41011                attribute.ConstructorArguments[0].Values.IsDefaultOrEmpty)
 1012            {
 01013                return false;
 1014            }
 1015
 41016            ImmutableArray<string>.Builder values = ImmutableArray.CreateBuilder<string>();
 1017
 221018            foreach (TypedConstant value in attribute.ConstructorArguments[0].Values)
 1019            {
 71020                if (value.Value is not int chatType ||
 71021                    !TelegramChatTypeFacts.TryMapToTelegramValue(chatType, out string mappedValue))
 1022                {
 01023                    return false;
 1024                }
 1025
 71026                values.Add(mappedValue);
 1027            }
 1028
 41029            builder.Add(new GeneratedFilter(generatedKind, values.ToImmutable(), LongValues: []));
 1030        }
 1031
 1801032        return true;
 01033    }
 1034
 1035    private static bool AppendSenderIdFilters(
 1036        ImmutableArray<GeneratedFilter>.Builder builder,
 1037        ISymbol symbol,
 1038        string attributeMetadataName,
 1039        string generatedKind)
 1040    {
 3661041        foreach (AttributeData attribute in TelegramHandlerSymbols.GetAttributes(
 1801042                     symbol,
 1801043                     attributeMetadataName,
 1801044                     inherit: true))
 1045        {
 31046            if (attribute.ConstructorArguments.Length != 1)
 1047            {
 01048                return false;
 1049            }
 1050
 31051            ImmutableArray<TypedConstant> arguments = attribute.ConstructorArguments[0].Values;
 1052
 31053            if (arguments.IsDefault ||
 51054                arguments.Any(static value => value.Value is not long id || id <= 0))
 1055            {
 01056                return false;
 1057            }
 1058
 31059            builder.Add(new GeneratedFilter(
 31060                generatedKind,
 31061                StringValues: [],
 51062                arguments.Select(static value => (long)value.Value!).ToImmutableArray()));
 1063        }
 1064
 1801065        return true;
 01066    }
 1067
 1068    private static void AppendMarkerFilter(
 1069        ImmutableArray<GeneratedFilter>.Builder builder,
 1070        ISymbol symbol,
 1071        TelegramBuiltInFilterSpec spec)
 1072    {
 18001073        if (TelegramHandlerSymbols.HasAttribute(symbol, spec.AttributeMetadataName, inherit: true))
 1074        {
 101075            builder.Add(new GeneratedFilter(spec.GeneratedKind, StringValues: [], LongValues: []));
 1076        }
 18001077    }
 1078
 1079    private static bool SupportsGeneratedFilter(
 1080        GeneratedFilter filter,
 1081        GeneratedHandlerKind handlerKind)
 1082    {
 231083        return TelegramBuiltInFilterFacts.TryGetSpecByGeneratedKind(filter.Kind, out TelegramBuiltInFilterSpec spec) &&
 231084               TelegramBuiltInFilterFacts.SupportsRouteKind(spec.Target, ToMetadataRouteKind(handlerKind));
 1085    }
 1086
 1087    private static bool TryPrepareCustomFilters(
 1088        ImmutableArray<GeneratedFilter> filters,
 1089        GeneratedHandlerKind handlerKind,
 1090        out ImmutableArray<GeneratedFilter> preparedFilters)
 1091    {
 451092        ImmutableArray<GeneratedFilter>.Builder builder = ImmutableArray.CreateBuilder<GeneratedFilter>(filters.Length);
 1093
 1421094        foreach (GeneratedFilter filter in filters)
 1095        {
 261096            if (filter.CustomTypeName is null)
 1097            {
 231098                builder.Add(filter);
 231099                continue;
 1100            }
 1101
 31102            if (!TryResolveCustomFilterContextType(filter, handlerKind, out string contextTypeName))
 1103            {
 01104                preparedFilters = [];
 01105                return false;
 1106            }
 1107
 31108            builder.Add(filter with { CustomContextTypeName = contextTypeName });
 1109        }
 1110
 451111        preparedFilters = builder.ToImmutable();
 451112        return true;
 1113    }
 1114
 1115    private static bool TryResolveCustomFilterContextType(
 1116        GeneratedFilter filter,
 1117        GeneratedHandlerKind handlerKind,
 1118        out string contextTypeName)
 1119    {
 31120        string expectedContext = handlerKind switch
 31121        {
 01122            GeneratedHandlerKind.Callback => TelegramHandlerSymbols.CallbackQueryContext,
 01123            GeneratedHandlerKind.ChatMember => TelegramHandlerSymbols.ChatMemberUpdatedContext,
 31124            _ => TelegramHandlerSymbols.MessageContext
 31125        };
 1126
 31127        if (filter.CustomContextMetadataNames.Contains(expectedContext, StringComparer.Ordinal))
 1128        {
 21129            contextTypeName = ToFullyQualifiedTypeName(expectedContext);
 21130            return true;
 1131        }
 1132
 11133        if (filter.CustomContextMetadataNames.Contains(TelegramHandlerSymbols.TelegramUpdateContext, StringComparer.Ordi
 1134        {
 11135            contextTypeName = ToFullyQualifiedTypeName(TelegramHandlerSymbols.TelegramUpdateContext);
 11136            return true;
 1137        }
 1138
 01139        contextTypeName = string.Empty;
 01140        return false;
 1141    }
 1142
 1143    private static string ToFullyQualifiedTypeName(string metadataName)
 1144    {
 31145        return $"global::{metadataName}";
 1146    }
 1147
 1148    private static string CanonicalizeChatUsername(string? username)
 1149    {
 11150        if (username is null)
 1151        {
 01152            return string.Empty;
 1153        }
 1154
 11155        string value = username.Trim();
 1156
 11157        if (value.StartsWith("@", StringComparison.Ordinal))
 1158        {
 11159            return value.Substring(1);
 1160        }
 1161
 01162        return value;
 1163    }
 1164
 1165    private static bool HasChatMemberTransitionAttributes(IMethodSymbol method)
 1166    {
 381167        return TelegramHandlerSymbols.HasAttribute(method.ContainingType, TelegramHandlerSymbols.ChatMemberTransitionAtt
 381168               TelegramHandlerSymbols.HasAttribute(method, TelegramHandlerSymbols.ChatMemberTransitionAttribute, inherit
 381169               TelegramHandlerSymbols.HasAttribute(method.ContainingType, TelegramHandlerSymbols.ChatMemberChangedAttrib
 381170               TelegramHandlerSymbols.HasAttribute(method, TelegramHandlerSymbols.ChatMemberChangedAttribute, inherit: t
 1171    }
 1172
 1173    private static bool TryBuildRoleRequirements(
 1174        IMethodSymbol method,
 1175        out ImmutableArray<int> roleRequirements)
 1176    {
 451177        ImmutableArray<int>.Builder builder = ImmutableArray.CreateBuilder<int>();
 1178
 941179        foreach (AttributeData attribute in TelegramHandlerSymbols.GetAttributes(method.ContainingType, TelegramHandlerS
 451180                     .Concat(TelegramHandlerSymbols.GetAttributes(method, TelegramHandlerSymbols.RequireTelegramRoleAttr
 1181        {
 21182            if (!TelegramMemberStatusFacts.TryGetRoleRequirementMask(attribute, out int allowedStatuses) ||
 21183                !TelegramMemberStatusFacts.IsValid(allowedStatuses))
 1184            {
 01185                roleRequirements = [];
 01186                return false;
 1187            }
 1188
 21189            builder.Add(allowedStatuses);
 1190        }
 1191
 451192        roleRequirements = builder.ToImmutable();
 451193        return true;
 01194    }
 1195
 1196    private static bool TryBuildChatMemberTransitions(
 1197        IMethodSymbol method,
 1198        out ImmutableArray<GeneratedChatMemberTransition> transitions)
 1199    {
 71200        ImmutableArray<GeneratedChatMemberTransition>.Builder builder = ImmutableArray.CreateBuilder<GeneratedChatMember
 1201
 261202        foreach (AttributeData attribute in TelegramHandlerSymbols.GetAttributes(method.ContainingType, TelegramHandlerS
 71203                     .Concat(TelegramHandlerSymbols.GetAttributes(method, TelegramHandlerSymbols.ChatMemberTransitionAtt
 1204        {
 61205            if (attribute.ConstructorArguments.Length == 0 ||
 61206                attribute.ConstructorArguments[0].Value is not int transition ||
 61207                !TelegramMemberStatusFacts.TryMapTransition(transition, out int oldStatus, out int newStatus))
 1208            {
 01209                transitions = [];
 01210                return false;
 1211            }
 1212
 61213            builder.Add(new GeneratedChatMemberTransition(oldStatus, newStatus));
 1214        }
 1215
 161216        foreach (AttributeData attribute in TelegramHandlerSymbols.GetAttributes(method.ContainingType, TelegramHandlerS
 71217                     .Concat(TelegramHandlerSymbols.GetAttributes(method, TelegramHandlerSymbols.ChatMemberChangedAttrib
 1218        {
 11219            if (attribute.ConstructorArguments.Length < 2 ||
 11220                attribute.ConstructorArguments[0].Value is not int oldStatus ||
 11221                attribute.ConstructorArguments[1].Value is not int newStatus ||
 11222                !TelegramMemberStatusFacts.IsValid(oldStatus) ||
 11223                !TelegramMemberStatusFacts.IsValid(newStatus))
 1224            {
 01225                transitions = [];
 01226                return false;
 1227            }
 1228
 11229            builder.Add(new GeneratedChatMemberTransition(oldStatus, newStatus));
 1230        }
 1231
 71232        transitions = builder.ToImmutable();
 71233        return true;
 01234    }
 1235
 1236    private static TelegramHandlerMetadataRouteKind ToMetadataRouteKind(GeneratedHandlerKind handlerKind)
 1237    {
 231238        return handlerKind switch
 231239        {
 01240            GeneratedHandlerKind.Command => TelegramHandlerMetadataRouteKind.Command,
 191241            GeneratedHandlerKind.Message => TelegramHandlerMetadataRouteKind.Message,
 41242            GeneratedHandlerKind.Callback => TelegramHandlerMetadataRouteKind.Callback,
 01243            GeneratedHandlerKind.ChatMember => TelegramHandlerMetadataRouteKind.ChatMember,
 01244            _ => TelegramHandlerMetadataRouteKind.Message
 231245        };
 1246    }
 1247
 1248    private static bool IsInvalidCustomFilterType(ITypeSymbol type)
 1249    {
 31250        return IsInvalidConcreteNamedType(type);
 1251    }
 1252
 1253    private static bool TryGetTelegramFilterContextTypes(
 1254        ITypeSymbol type,
 1255        ITypeSymbol? attributeType,
 1256        out ImmutableArray<string> contextTypes)
 1257    {
 31258        ImmutableArray<string>.Builder builder = ImmutableArray.CreateBuilder<string>();
 1259
 121260        foreach (INamedTypeSymbol candidate in type.AllInterfaces)
 1261        {
 31262            if (attributeType is null)
 1263            {
 21264                if (string.Equals(
 21265                        candidate.OriginalDefinition.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat),
 21266                        TelegramHandlerSymbols.GenericTelegramFilter,
 21267                        StringComparison.Ordinal) &&
 21268                    candidate.TypeArguments.Length == 1)
 1269                {
 21270                    builder.Add(candidate.TypeArguments[0].ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)
 1271                }
 1272
 21273                continue;
 1274            }
 1275
 11276            if (string.Equals(
 11277                    candidate.OriginalDefinition.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat),
 11278                    TelegramHandlerSymbols.GenericParameterizedTelegramFilter,
 11279                    StringComparison.Ordinal) &&
 11280                candidate.TypeArguments.Length == 2 &&
 11281                SymbolEqualityComparer.Default.Equals(candidate.TypeArguments[1], attributeType))
 1282            {
 11283                builder.Add(candidate.TypeArguments[0].ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat));
 1284            }
 1285        }
 1286
 31287        contextTypes = builder.ToImmutable();
 31288        return contextTypes.Length > 0;
 1289    }
 1290
 1291    private static string? GetModuleName(INamedTypeSymbol handlerType)
 1292    {
 421293        AttributeData? attribute = TelegramHandlerSymbols.GetFirstAttribute(
 421294            handlerType,
 421295            TelegramHandlerSymbols.TelegramModuleAttribute);
 1296
 421297        if (attribute?.ConstructorArguments.Length > 0 &&
 421298            attribute.ConstructorArguments[0].Value is string name &&
 421299            !string.IsNullOrWhiteSpace(name))
 1300        {
 51301            return name.Trim();
 1302        }
 1303
 371304        return null;
 1305    }
 1306
 1307    private static bool TryGetExpectedContextType(GeneratedHandlerKind kind, out string contextType)
 1308    {
 401309        contextType = kind switch
 401310        {
 61311            GeneratedHandlerKind.Callback => TelegramHandlerSymbols.CallbackQueryContext,
 71312            GeneratedHandlerKind.ChatMember => TelegramHandlerSymbols.ChatMemberUpdatedContext,
 271313            _ => TelegramHandlerSymbols.MessageContext
 401314        };
 401315        return true;
 1316    }
 1317
 1318    private static bool HasExactlyOneExpectedContext(IMethodSymbol method, string expectedContextType)
 1319    {
 401320        IParameterSymbol[] contextParameters = method.Parameters
 401321            .Where(static parameter =>
 561322                TelegramHandlerSymbols.IsType(parameter.Type, TelegramHandlerSymbols.MessageContext) ||
 561323                TelegramHandlerSymbols.IsType(parameter.Type, TelegramHandlerSymbols.CallbackQueryContext) ||
 561324                TelegramHandlerSymbols.IsType(parameter.Type, TelegramHandlerSymbols.ChatMemberUpdatedContext) ||
 561325                TelegramHandlerSymbols.IsType(parameter.Type, TelegramHandlerSymbols.TelegramUpdateContext))
 401326            .ToArray();
 1327
 401328        return contextParameters.Length == 1 &&
 401329               TelegramHandlerSymbols.IsType(contextParameters[0].Type, expectedContextType);
 1330    }
 1331
 1332    private static bool HasValidCallbackPayloadParameter(
 1333        IMethodSymbol method,
 1334        ITypeSymbol? callbackPayloadType)
 1335    {
 401336        if (callbackPayloadType is null)
 1337        {
 371338            return true;
 1339        }
 1340
 31341        if (IsInvalidCallbackPayloadType(callbackPayloadType))
 1342        {
 01343            return false;
 1344        }
 1345
 31346        return method.Parameters.Count(parameter =>
 111347            SymbolEqualityComparer.Default.Equals(parameter.Type, callbackPayloadType)) == 1;
 1348    }
 1349
 1350    private static bool IsInvalidCallbackPayloadType(ITypeSymbol type)
 1351    {
 31352        return IsInvalidConcreteNamedType(type);
 1353    }
 1354
 1355    private static bool IsAccessibleFromGeneratedCode(INamedTypeSymbol type)
 1356    {
 81357        for (INamedTypeSymbol? current = type; current is not null; current = current.ContainingType)
 1358        {
 21359            if (current.DeclaredAccessibility is not (Accessibility.Public or Accessibility.Internal))
 1360            {
 01361                return false;
 1362            }
 1363        }
 1364
 21365        return true;
 1366    }
 1367
 1368    private static bool TryGetCallbackDataFields(
 1369        INamedTypeSymbol type,
 1370        out bool usesConstructor,
 1371        out ImmutableArray<GeneratedCallbackDataField> fields)
 1372    {
 21373        usesConstructor = false;
 21374        fields = ImmutableArray<GeneratedCallbackDataField>.Empty;
 1375
 21376        IMethodSymbol? constructor = type.InstanceConstructors
 21377            .Where(static candidate =>
 41378                candidate.DeclaredAccessibility == Accessibility.Public &&
 41379                candidate.Parameters.Length > 0)
 21380            .OrderByDescending(static candidate => candidate.Parameters.Length)
 21381            .FirstOrDefault();
 1382
 21383        if (constructor is not null)
 1384        {
 21385            return TryGetConstructorCallbackDataFields(type, constructor, out usesConstructor, out fields);
 1386        }
 1387
 01388        return TryGetPropertyCallbackDataFields(type, out fields);
 1389    }
 1390
 1391    private static bool TryGetConstructorCallbackDataFields(
 1392        INamedTypeSymbol type,
 1393        IMethodSymbol constructor,
 1394        out bool usesConstructor,
 1395        out ImmutableArray<GeneratedCallbackDataField> fields)
 1396    {
 21397        usesConstructor = true;
 21398        fields = ImmutableArray<GeneratedCallbackDataField>.Empty;
 1399
 21400        Dictionary<string, IPropertySymbol> properties = type
 21401            .GetMembers()
 21402            .OfType<IPropertySymbol>()
 21403            .Where(static property =>
 101404                !property.IsStatic &&
 101405                property.GetMethod is { DeclaredAccessibility: Accessibility.Public })
 101406            .ToDictionary(static property => property.Name, StringComparer.OrdinalIgnoreCase);
 21407        ImmutableArray<GeneratedCallbackDataField>.Builder builder = ImmutableArray.CreateBuilder<GeneratedCallbackDataF
 1408
 201409        foreach (IParameterSymbol parameter in constructor.Parameters)
 1410        {
 81411            if (!properties.TryGetValue(parameter.Name, out IPropertySymbol? property) ||
 81412                !TryGetCallbackDataFieldKind(property.Type, out GeneratedCallbackDataFieldKind kind))
 1413            {
 01414                return false;
 1415            }
 1416
 81417            builder.Add(new GeneratedCallbackDataField(
 81418                property.Name,
 81419                property.Type.ToDisplayString(FullyQualifiedFormat),
 81420                kind));
 1421        }
 1422
 21423        fields = builder.ToImmutable();
 21424        return true;
 1425    }
 1426
 1427    private static bool TryGetPropertyCallbackDataFields(
 1428        INamedTypeSymbol type,
 1429        out ImmutableArray<GeneratedCallbackDataField> fields)
 1430    {
 01431        ImmutableArray<GeneratedCallbackDataField>.Builder builder = ImmutableArray.CreateBuilder<GeneratedCallbackDataF
 1432
 01433        foreach (IPropertySymbol property in type
 01434                     .GetMembers()
 01435                     .OfType<IPropertySymbol>()
 01436                     .Where(static property =>
 01437                         !property.IsStatic &&
 01438                         property.GetMethod is { DeclaredAccessibility: Accessibility.Public }))
 1439        {
 01440            if (property.SetMethod is not { DeclaredAccessibility: Accessibility.Public } ||
 01441                !TryGetCallbackDataFieldKind(property.Type, out GeneratedCallbackDataFieldKind kind))
 1442            {
 01443                return false;
 1444            }
 1445
 01446            builder.Add(new GeneratedCallbackDataField(
 01447                property.Name,
 01448                property.Type.ToDisplayString(FullyQualifiedFormat),
 01449                kind));
 1450        }
 1451
 01452        fields = builder.ToImmutable();
 01453        return true;
 01454    }
 1455
 1456    private static bool TryGetCallbackDataFieldKind(
 1457        ITypeSymbol type,
 1458        out GeneratedCallbackDataFieldKind kind)
 1459    {
 81460        kind = type.SpecialType switch
 81461        {
 21462            SpecialType.System_String => GeneratedCallbackDataFieldKind.String,
 01463            SpecialType.System_Int32 => GeneratedCallbackDataFieldKind.Int32,
 21464            SpecialType.System_Int64 => GeneratedCallbackDataFieldKind.Int64,
 21465            SpecialType.System_Boolean => GeneratedCallbackDataFieldKind.Boolean,
 21466            _ => default
 81467        };
 1468
 81469        if (type.SpecialType is SpecialType.System_String or
 81470            SpecialType.System_Int32 or
 81471            SpecialType.System_Int64 or
 81472            SpecialType.System_Boolean)
 1473        {
 61474            return true;
 1475        }
 1476
 21477        if (type.TypeKind == TypeKind.Enum)
 1478        {
 21479            kind = GeneratedCallbackDataFieldKind.Enum;
 21480            return true;
 1481        }
 1482
 01483        return false;
 1484    }
 1485
 1486    private static bool IsInvalidConcreteNamedType(ITypeSymbol type)
 1487    {
 61488        return type.TypeKind is TypeKind.Interface or TypeKind.TypeParameter ||
 61489               type is not INamedTypeSymbol namedType ||
 61490               namedType.IsAbstract ||
 61491               namedType.IsUnboundGenericType ||
 61492               namedType.TypeArguments.Any(static argument => argument.TypeKind == TypeKind.TypeParameter);
 1493    }
 1494
 1495    private static bool IsSupportedReturnType(ITypeSymbol returnType)
 1496    {
 401497        return TelegramHandlerSymbols.IsType(returnType, TelegramHandlerSymbols.Task) ||
 401498               TelegramHandlerSymbols.IsType(returnType, TelegramHandlerSymbols.ValueTask);
 1499    }
 1500
 1501    private static bool TryGetErrorReturnKind(
 1502        ITypeSymbol returnType,
 1503        out GeneratedErrorReturnKind returnKind)
 1504    {
 31505        if (TelegramHandlerSymbols.IsType(returnType, TelegramHandlerSymbols.TelegramErrorHandlingResult))
 1506        {
 11507            returnKind = GeneratedErrorReturnKind.Sync;
 11508            return true;
 1509        }
 1510
 21511        if (returnType is INamedTypeSymbol
 21512            {
 21513                IsGenericType: true,
 21514                TypeArguments.Length: 1
 21515            } namedType &&
 21516            TelegramHandlerSymbols.IsType(namedType.TypeArguments[0], TelegramHandlerSymbols.TelegramErrorHandlingResult
 1517        {
 21518            GeneratedErrorReturnKind? matchedReturnKind = namedType.ConstructedFrom
 21519                .ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat) switch
 21520            {
 11521                "System.Threading.Tasks.Task<TResult>" => GeneratedErrorReturnKind.Task,
 11522                "System.Threading.Tasks.ValueTask<TResult>" => GeneratedErrorReturnKind.ValueTask,
 01523                _ => null
 21524            };
 1525
 21526            if (matchedReturnKind is not null)
 1527            {
 21528                returnKind = matchedReturnKind.Value;
 21529                return true;
 1530            }
 1531        }
 1532
 01533        returnKind = default;
 01534        return false;
 1535    }
 1536
 1537    private static bool IsValidCommand(string? command)
 1538    {
 121539        return command is not null &&
 121540               !string.IsNullOrWhiteSpace(command) &&
 121541               !command.StartsWith("/", StringComparison.Ordinal) &&
 121542               !command.Contains("@", StringComparison.Ordinal) &&
 121543               !command.Any(char.IsWhiteSpace);
 1544    }
 1545
 1546    private static string? GetConstructorString(AttributeData attribute)
 1547    {
 251548        if (attribute.ConstructorArguments.Length > 0 &&
 251549            attribute.ConstructorArguments[0].Value is string value)
 1550        {
 251551            return value.Trim();
 1552        }
 1553
 01554        return null;
 1555    }
 1556
 1557    private static bool TryGetPrefixes(
 1558        AttributeData attribute,
 1559        out ImmutableArray<string> prefixes)
 1560    {
 161561        prefixes = ["/"];
 1562
 401563        foreach (KeyValuePair<string, TypedConstant> argument in attribute.NamedArguments)
 1564        {
 61565            if (!string.Equals(argument.Key, "Prefixes", StringComparison.Ordinal))
 1566            {
 1567                continue;
 1568            }
 1569
 41570            if (argument.Value.Values.IsDefaultOrEmpty)
 1571            {
 01572                return false;
 1573            }
 1574
 41575            ImmutableArray<string>.Builder builder = ImmutableArray.CreateBuilder<string>();
 41576            HashSet<string> seen = new HashSet<string>(StringComparer.Ordinal);
 1577
 191578            foreach (TypedConstant value in argument.Value.Values)
 1579            {
 61580                if (value.Value is not string prefix ||
 61581                    string.IsNullOrWhiteSpace(prefix))
 1582                {
 11583                    return false;
 1584                }
 1585
 51586                string normalized = prefix.Trim();
 1587
 51588                if (seen.Add(normalized))
 1589                {
 51590                    builder.Add(normalized);
 1591                }
 1592            }
 1593
 31594            prefixes = builder.ToImmutable();
 31595            return true;
 1596        }
 1597
 121598        return true;
 1599    }
 1600
 1601    private static int GetCommandPrefixMode(AttributeData attribute)
 1602    {
 381603        foreach (KeyValuePair<string, TypedConstant> argument in attribute.NamedArguments)
 1604        {
 41605            if (string.Equals(argument.Key, "PrefixMode", StringComparison.Ordinal) &&
 41606                argument.Value.Value is int value)
 1607            {
 21608                return value;
 1609            }
 1610        }
 1611
 141612        return CommandPrefixModeRequired;
 1613    }
 1614
 1615    private static bool HasInvalidNoPrefixConfiguration(
 1616        AttributeData attribute,
 1617        int prefixMode)
 1618    {
 151619        return prefixMode == CommandPrefixModeNoPrefix && HasNamedArgument(attribute, "Prefixes");
 1620    }
 1621
 1622    private static bool IsSupportedCommandPrefixMode(int value)
 1623    {
 151624        return value is CommandPrefixModeRequired or
 151625            CommandPrefixModeOptional or
 151626            CommandPrefixModeNoPrefix;
 1627    }
 1628
 1629    private static bool HasNamedArgument(
 1630        AttributeData attribute,
 1631        string name)
 1632    {
 31633        return attribute.NamedArguments.Any(argument => string.Equals(argument.Key, name, StringComparison.Ordinal));
 1634    }
 1635
 1636    private static bool CommandPatternStartsWithPrefix(
 1637        string pattern,
 1638        ImmutableArray<string> prefixes)
 1639    {
 21640        string normalized = pattern.Trim();
 1641
 51642        return prefixes.Any(prefix => normalized.StartsWith(prefix, StringComparison.Ordinal));
 1643    }
 1644
 1645    private static bool GetNamedBool(
 1646        AttributeData attribute,
 1647        string name,
 1648        bool defaultValue)
 1649    {
 771650        foreach (KeyValuePair<string, TypedConstant> argument in attribute.NamedArguments)
 1651        {
 81652            if (string.Equals(argument.Key, name, StringComparison.Ordinal) &&
 81653                argument.Value.Value is bool value)
 1654            {
 11655                return value;
 1656            }
 1657        }
 1658
 301659        return defaultValue;
 1660    }
 1661
 1662    private static bool TryParseTemplateRouteValues(
 1663        string? template,
 1664        out ImmutableDictionary<string, GeneratedRouteValue> routeValues)
 1665    {
 71666        routeValues = ImmutableDictionary<string, GeneratedRouteValue>.Empty;
 1667
 71668        if (template is null ||
 71669            string.IsNullOrWhiteSpace(template))
 1670        {
 01671            return false;
 1672        }
 1673
 71674        ImmutableDictionary<string, GeneratedRouteValue>.Builder values = ImmutableDictionary.CreateBuilder<string, Gene
 71675        Regex placeholderRegex = new Regex(
 71676            @"\{(?<name>[A-Za-z_][A-Za-z0-9_]*)(?:(?<nameOptional>\?)|:(?<constraint>[A-Za-z][A-Za-z0-9_]*)(?<constraint
 71677            RegexOptions.CultureInvariant);
 71678        int position = 0;
 1679
 261680        foreach (Match match in placeholderRegex.Matches(template))
 1681        {
 61682            if (match.Index != position &&
 61683                ContainsBrace(template.Substring(position, match.Index - position)))
 1684            {
 01685                return false;
 1686            }
 1687
 61688            string name = match.Groups["name"].Value;
 61689            bool hasNameOptional = match.Groups["nameOptional"].Success;
 61690            bool hasConstraintOptional = match.Groups["constraintOptional"].Success;
 61691            string constraint = match.Groups["constraint"].Success
 61692                ? match.Groups["constraint"].Value
 61693                : "string";
 1694
 61695            if (values.ContainsKey(name) ||
 61696                constraint is not ("string" or "int" or "long"))
 1697            {
 01698                return false;
 1699            }
 1700
 61701            values.Add(name, new GeneratedRouteValue(constraint, hasNameOptional || hasConstraintOptional));
 61702            position = match.Index + match.Length;
 1703        }
 1704
 71705        if (ContainsBrace(template.Substring(position)))
 1706        {
 11707            return false;
 1708        }
 1709
 61710        routeValues = values.ToImmutable();
 61711        return true;
 01712    }
 1713
 1714    private static bool TryParseRegexRouteValues(
 1715        string? pattern,
 1716        out ImmutableDictionary<string, GeneratedRouteValue> routeValues)
 1717    {
 11718        routeValues = ImmutableDictionary<string, GeneratedRouteValue>.Empty;
 1719
 11720        if (string.IsNullOrWhiteSpace(pattern))
 1721        {
 01722            return false;
 1723        }
 1724
 1725        Regex regex;
 1726
 1727        try
 1728        {
 11729            regex = new Regex(pattern, RegexOptions.CultureInvariant);
 11730        }
 01731        catch (ArgumentException)
 1732        {
 01733            return false;
 1734        }
 1735
 11736        ImmutableDictionary<string, GeneratedRouteValue>.Builder values = ImmutableDictionary.CreateBuilder<string, Gene
 1737
 61738        foreach (string name in regex.GetGroupNames())
 1739        {
 21740            if (!int.TryParse(name, out _))
 1741            {
 11742                values[name] = new GeneratedRouteValue(Constraint: null, IsOptional: false);
 1743            }
 1744        }
 1745
 11746        routeValues = values.ToImmutable();
 11747        return true;
 01748    }
 1749
 1750    private static bool ContainsBrace(string value)
 1751    {
 131752        return value.IndexOf("{", StringComparison.Ordinal) >= 0 ||
 131753               value.IndexOf("}", StringComparison.Ordinal) >= 0;
 1754    }
 1755
 1756    private static bool TryResolveRouteValueNames(
 1757        IMethodSymbol method,
 1758        ImmutableArray<GeneratedRoute> routes,
 1759        out ImmutableHashSet<string> routeValueNames)
 1760    {
 401761        routeValueNames = ImmutableHashSet<string>.Empty.WithComparer(StringComparer.Ordinal);
 1762
 401763        GeneratedRoute[] routeValueRoutes = routes
 421764            .Where(static route => route.RouteValues.Count > 0)
 401765            .ToArray();
 1766
 401767        if (routeValueRoutes.Length == 0)
 1768        {
 361769            return true;
 1770        }
 1771
 91772        if (routes.Any(static route => route.RouteValues.Count == 0))
 1773        {
 01774            return false;
 1775        }
 1776
 41777        string[] firstNames = routeValueRoutes[0].RouteValues.Keys.OrderBy(static name => name, StringComparer.Ordinal).
 1778
 101779        foreach (GeneratedRoute route in routeValueRoutes.Skip(1))
 1780        {
 11781            string[] names = route.RouteValues.Keys.OrderBy(static name => name, StringComparer.Ordinal).ToArray();
 1782
 11783            if (!firstNames.SequenceEqual(names, StringComparer.Ordinal))
 1784            {
 01785                return false;
 1786            }
 1787        }
 1788
 41789        ImmutableHashSet<string>.Builder builder = ImmutableHashSet.CreateBuilder<string>(StringComparer.Ordinal);
 1790
 161791        foreach (string name in firstNames)
 1792        {
 121793            IParameterSymbol? parameter = method.Parameters.FirstOrDefault(parameter => string.Equals(parameter.Name, na
 1794
 41795            if (parameter is null ||
 41796                !IsSupportedRouteValueParameterType(parameter.Type))
 1797            {
 01798                return false;
 1799            }
 1800
 181801            foreach (GeneratedRoute route in routeValueRoutes)
 1802            {
 51803                GeneratedRouteValue routeValue = route.RouteValues[name];
 1804
 51805                if (routeValue.IsOptional != IsNullableRouteValueParameter(parameter.Type))
 1806                {
 01807                    return false;
 1808                }
 1809
 51810                if (routeValue.Constraint is not null &&
 51811                    !RouteConstraintMatchesParameter(routeValue.Constraint, parameter.Type))
 1812                {
 01813                    return false;
 1814                }
 1815            }
 1816
 41817            builder.Add(name);
 1818        }
 1819
 41820        routeValueNames = builder.ToImmutable();
 41821        return true;
 01822    }
 1823
 1824    private static bool IsSupportedRouteValueParameterType(ITypeSymbol type)
 1825    {
 41826        return IsRouteValueParameterType(type);
 1827    }
 1828
 1829    private static bool IsNullableRouteValueParameter(ITypeSymbol type)
 1830    {
 51831        if (type.SpecialType == SpecialType.System_String)
 1832        {
 01833            return type.NullableAnnotation == NullableAnnotation.Annotated;
 1834        }
 1835
 51836        return type is INamedTypeSymbol
 51837        {
 51838            OriginalDefinition.SpecialType: SpecialType.System_Nullable_T
 51839        };
 1840    }
 1841
 1842    private static SpecialType GetRouteValueParameterSpecialType(ITypeSymbol type)
 1843    {
 41844        if (type is INamedTypeSymbol
 41845            {
 41846                OriginalDefinition.SpecialType: SpecialType.System_Nullable_T,
 41847                TypeArguments.Length: 1
 41848            } nullable)
 1849        {
 11850            return nullable.TypeArguments[0].SpecialType;
 1851        }
 1852
 31853        return type.SpecialType;
 1854    }
 1855
 1856    private static bool RouteConstraintMatchesParameter(
 1857        string constraint,
 1858        ITypeSymbol parameterType)
 1859    {
 41860        SpecialType specialType = GetRouteValueParameterSpecialType(parameterType);
 1861
 41862        return constraint switch
 41863        {
 01864            "string" => specialType == SpecialType.System_String,
 11865            "int" => specialType == SpecialType.System_Int32,
 31866            "long" => specialType == SpecialType.System_Int64,
 01867            _ => false
 41868        };
 1869    }
 1870
 1871    private static string GetRouteValueTypeName(GeneratedRouteValue routeValue)
 1872    {
 41873        return routeValue.Constraint switch
 41874        {
 11875            "int" => "int",
 31876            "long" => "long",
 01877            _ => "string"
 41878        };
 1879    }
 1880
 1881    private static ImmutableArray<GeneratedTextFilter> BuildTextFilters(
 1882        IMethodSymbol method,
 1883        bool includeClassRouteAttributes)
 1884    {
 291885        ImmutableArray<GeneratedTextFilter>.Builder builder = ImmutableArray.CreateBuilder<GeneratedTextFilter>();
 1886
 621887        foreach (AttributeData attribute in GetRouteAttributes(
 291888                     method,
 291889                     TelegramHandlerSymbols.TextAttribute,
 291890                     includeClassRouteAttributes))
 1891        {
 21892            string? value = attribute.ConstructorArguments.Length > 0
 21893                ? attribute.ConstructorArguments[0].Value as string
 21894                : null;
 1895
 21896            if (value is null ||
 21897                string.IsNullOrWhiteSpace(value))
 1898            {
 1899                continue;
 1900            }
 1901
 21902            int mode = attribute.ConstructorArguments.Length > 1 &&
 21903                       attribute.ConstructorArguments[1].Value is int modeValue
 21904                ? modeValue
 21905                : 0;
 21906            bool ignoreCase = attribute.ConstructorArguments.Length > 2 &&
 21907                             attribute.ConstructorArguments[2].Value is bool ignoreCaseValue
 21908                ? ignoreCaseValue
 21909                : true;
 1910
 21911            builder.Add(new GeneratedTextFilter(value, mode, ignoreCase));
 1912        }
 1913
 291914        return builder.ToImmutable();
 1915    }
 1916
 1917    private static IReadOnlyList<AttributeData> GetRouteAttributes(
 1918        IMethodSymbol method,
 1919        string metadataName,
 1920        bool includeClassRouteAttributes)
 1921    {
 6571922        IReadOnlyList<AttributeData> methodAttributes = TelegramHandlerSymbols.GetAttributes(method, metadataName, inher
 1923
 6571924        if (!includeClassRouteAttributes)
 1925        {
 6031926            return methodAttributes;
 1927        }
 1928
 541929        IReadOnlyList<AttributeData> typeAttributes = TelegramHandlerSymbols.GetAttributes(method.ContainingType, metada
 1930
 541931        return typeAttributes.Count == 0
 541932            ? methodAttributes
 541933            : typeAttributes.Concat(methodAttributes).ToArray();
 1934    }
 1935
 1936    private static IReadOnlyList<AttributeData> GetGenericRouteAttributes(
 1937        IMethodSymbol method,
 1938        string metadataName,
 1939        bool includeClassRouteAttributes)
 1940    {
 451941        IReadOnlyList<AttributeData> methodAttributes = TelegramHandlerSymbols.GetGenericAttributes(method, metadataName
 1942
 451943        if (!includeClassRouteAttributes)
 1944        {
 411945            return methodAttributes;
 1946        }
 1947
 41948        IReadOnlyList<AttributeData> typeAttributes = TelegramHandlerSymbols.GetGenericAttributes(method.ContainingType,
 1949
 41950        return typeAttributes.Count == 0
 41951            ? methodAttributes
 41952            : typeAttributes.Concat(methodAttributes).ToArray();
 1953    }
 1954
 1955    private static bool IsClassBasedHandlerType(INamedTypeSymbol type)
 1956    {
 1431957        return IsAssignableTo(type, TelegramHandlerSymbols.MessageHandler) ||
 1431958               IsAssignableTo(type, TelegramHandlerSymbols.CallbackHandler) ||
 1431959               IsAssignableTo(type, TelegramHandlerSymbols.ChatMemberUpdateHandler);
 1960    }
 1961
 1962    private static bool IsClassBasedRouteCompatible(
 1963        INamedTypeSymbol type,
 1964        GeneratedHandlerKind kind,
 1965        ITypeSymbol? callbackPayloadType)
 1966    {
 401967        if (!IsClassBasedHandlerType(type))
 1968        {
 361969            return true;
 1970        }
 1971
 41972        if (IsAssignableTo(type, TelegramHandlerSymbols.MessageHandler))
 1973        {
 21974            return kind is GeneratedHandlerKind.Command or GeneratedHandlerKind.Message;
 1975        }
 1976
 21977        if (TryGetCallbackHandlerPayloadType(type, out ITypeSymbol handlerPayloadType))
 1978        {
 11979            return kind == GeneratedHandlerKind.Callback &&
 11980                   callbackPayloadType is not null &&
 11981                   SymbolEqualityComparer.Default.Equals(handlerPayloadType, callbackPayloadType);
 1982        }
 1983
 11984        if (IsDirectlyAssignableTo(type, TelegramHandlerSymbols.CallbackHandler))
 1985        {
 01986            return kind == GeneratedHandlerKind.Callback &&
 01987                   callbackPayloadType is null;
 1988        }
 1989
 11990        return IsAssignableTo(type, TelegramHandlerSymbols.ChatMemberUpdateHandler) &&
 11991               kind == GeneratedHandlerKind.ChatMember;
 1992    }
 1993
 1994    private static ImmutableArray<IMethodSymbol> GetDeclaredHandleAsyncMethods(INamedTypeSymbol type)
 1995    {
 41996        return type.GetMembers("HandleAsync")
 41997            .OfType<IMethodSymbol>()
 41998            .Where(static method =>
 41999                method.DeclaredAccessibility == Accessibility.Public &&
 42000                !method.IsStatic &&
 42001                method.MethodKind == MethodKind.Ordinary &&
 42002                !method.IsGenericMethod)
 42003            .ToImmutableArray();
 2004    }
 2005
 2006    private static bool TryGetCallbackHandlerPayloadType(
 2007        INamedTypeSymbol type,
 2008        out ITypeSymbol payloadType)
 2009    {
 122010        for (INamedTypeSymbol? current = type; current is not null; current = current.BaseType)
 2011        {
 52012            if (current.OriginalDefinition.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat) ==
 52013                TelegramHandlerSymbols.GenericCallbackHandler &&
 52014                current.TypeArguments.Length == 1)
 2015            {
 12016                payloadType = current.TypeArguments[0];
 12017                return true;
 2018            }
 2019        }
 2020
 12021        payloadType = null!;
 12022        return false;
 2023    }
 2024
 2025    private static bool IsAssignableTo(INamedTypeSymbol type, string metadataName)
 2026    {
 28302027        for (INamedTypeSymbol? current = type; current is not null; current = current.BaseType)
 2028        {
 9872029            if (current.OriginalDefinition.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat) == metadataName
 9872030                current.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat) == metadataName)
 2031            {
 272032                return true;
 2033            }
 2034        }
 2035
 4282036        return false;
 2037    }
 2038
 2039    private static bool IsDirectlyAssignableTo(INamedTypeSymbol type, string metadataName)
 2040    {
 12041        return IsAssignableTo(type, metadataName) &&
 12042               !TryGetCallbackHandlerPayloadType(type, out _);
 2043    }
 2044
 2045    private static bool TryBuildStates(
 2046        IMethodSymbol method,
 2047        out ImmutableArray<string> states,
 2048        out string? sceneName)
 2049    {
 402050        ImmutableArray<string>.Builder builder = ImmutableArray.CreateBuilder<string>();
 402051        states = [];
 402052        sceneName = null;
 2053
 882054        foreach (AttributeData attribute in TelegramHandlerSymbols.GetAttributes(method.ContainingType, TelegramHandlerS
 402055                     .Concat(TelegramHandlerSymbols.GetAttributes(method, TelegramHandlerSymbols.StateAttribute, inherit
 2056        {
 42057            if (attribute.ConstructorArguments.Length > 0 &&
 42058                attribute.ConstructorArguments[0].Value is string state &&
 42059                !string.IsNullOrWhiteSpace(state))
 2060            {
 42061                builder.Add(state);
 2062            }
 2063        }
 2064
 402065        AttributeData[] hasGenericStateAttributes = GetGenericStateAttributes(method.ContainingType)
 402066            .Concat(GetGenericStateAttributes(method))
 402067            .ToArray();
 2068
 822069        foreach (AttributeData attribute in hasGenericStateAttributes)
 2070        {
 12071            if (TryResolveTypedState(attribute, out string state))
 2072            {
 12073                builder.Add(state);
 2074            }
 2075        }
 2076
 402077        AttributeData? sceneStep = TelegramHandlerSymbols.GetFirstAttribute(
 402078            method,
 402079            TelegramHandlerSymbols.SceneStepAttribute,
 402080            inherit: true);
 2081
 402082        if (sceneStep is not null)
 2083        {
 22084            if (builder.Count > 0 ||
 22085                !TryResolveSceneStep(method, sceneStep, out string sceneState, out sceneName))
 2086            {
 12087                return false;
 2088            }
 2089
 12090            builder.Add(sceneState);
 2091        }
 2092
 392093        states = builder.ToImmutable();
 392094        return true;
 2095    }
 2096
 2097    private static IEnumerable<AttributeData> GetGenericStateAttributes(ISymbol symbol)
 2098    {
 802099        return TelegramHandlerSymbols.GetGenericAttributes(
 802100            symbol,
 802101            TelegramHandlerSymbols.GenericStateAttribute,
 802102            inherit: true);
 2103    }
 2104
 2105    private static bool TryResolveTypedState(AttributeData attribute, out string state)
 2106    {
 12107        state = string.Empty;
 2108
 12109        if (attribute.AttributeClass is not { TypeArguments.Length: 1 } attributeType ||
 12110            attribute.ConstructorArguments.Length == 0 ||
 12111            attribute.ConstructorArguments[0].Value is not string stateName ||
 12112            string.IsNullOrWhiteSpace(stateName) ||
 12113            attributeType.TypeArguments[0] is not INamedTypeSymbol groupType)
 2114        {
 02115            return false;
 2116        }
 2117
 12118        if (!TryGetStateContainerPrefix(groupType, out string prefix))
 2119        {
 02120            return false;
 2121        }
 2122
 12123        IPropertySymbol? property = groupType
 12124            .GetMembers(stateName)
 12125            .OfType<IPropertySymbol>()
 12126            .FirstOrDefault(IsUsableStateProperty);
 2127
 12128        if (property is null || !IsPartialStateProperty(property))
 2129        {
 02130            return false;
 2131        }
 2132
 12133        state = $"{prefix}:{GetStateSegment(property)}";
 12134        return true;
 2135    }
 2136
 2137    private static bool TryResolveSceneStep(
 2138        IMethodSymbol method,
 2139        AttributeData attribute,
 2140        out string state,
 2141        out string? sceneName)
 2142    {
 22143        state = string.Empty;
 22144        sceneName = null;
 2145
 22146        if (!TryGetScenePrefix(method.ContainingType, out string prefix) ||
 22147            attribute.ConstructorArguments.Length == 0 ||
 22148            attribute.ConstructorArguments[0].Value is not string stateName ||
 22149            string.IsNullOrWhiteSpace(stateName))
 2150        {
 02151            return false;
 2152        }
 2153
 22154        IPropertySymbol? property = method.ContainingType
 22155            .GetMembers(stateName)
 22156            .OfType<IPropertySymbol>()
 22157            .FirstOrDefault(IsUsableStateProperty);
 2158
 22159        if (property is null || !IsPartialStateProperty(property))
 2160        {
 12161            return false;
 2162        }
 2163
 12164        sceneName = prefix;
 12165        state = $"{prefix}:{GetStateSegment(property)}";
 12166        return true;
 2167    }
 2168
 2169    private static bool TryGetStateContainerPrefix(INamedTypeSymbol type, out string prefix)
 2170    {
 202171        return TryGetStateGroupPrefix(type, out prefix) ||
 202172               TryGetScenePrefix(type, out prefix);
 2173    }
 2174
 2175    private static bool TryGetStateGroupPrefix(INamedTypeSymbol type, out string prefix)
 2176    {
 202177        return TryGetAttributeStringPrefix(type, TelegramHandlerSymbols.StateGroupAttribute, out prefix);
 2178    }
 2179
 2180    private static bool TryGetScenePrefix(INamedTypeSymbol type, out string prefix)
 2181    {
 202182        return TryGetAttributeStringPrefix(type, TelegramHandlerSymbols.SceneAttribute, out prefix);
 2183    }
 2184
 2185    private static bool TryGetAttributeStringPrefix(
 2186        INamedTypeSymbol type,
 2187        string metadataName,
 2188        out string prefix)
 2189    {
 402190        AttributeData? attribute = TelegramHandlerSymbols.GetFirstAttribute(type, metadataName);
 2191
 402192        if (attribute?.ConstructorArguments.Length > 0 &&
 402193            attribute.ConstructorArguments[0].Value is string value &&
 402194            !string.IsNullOrWhiteSpace(value))
 2195        {
 72196            prefix = value;
 72197            return true;
 2198        }
 2199
 332200        prefix = string.Empty;
 332201        return false;
 2202    }
 2203
 2204    private static string GetStateSegment(IPropertySymbol property)
 2205    {
 82206        AttributeData? attribute = TelegramHandlerSymbols.GetFirstAttribute(
 82207            property,
 82208            TelegramHandlerSymbols.StateValueAttribute);
 2209
 82210        if (attribute?.ConstructorArguments.Length > 0 &&
 82211            attribute.ConstructorArguments[0].Value is string value &&
 82212            !string.IsNullOrWhiteSpace(value))
 2213        {
 22214            return value;
 2215        }
 2216
 62217        return ToCamelCase(property.Name);
 2218    }
 2219
 2220    private static bool IsUsableStateProperty(IPropertySymbol property)
 2221    {
 112222        return property.DeclaredAccessibility == Accessibility.Public &&
 112223               property.IsStatic &&
 112224               TelegramHandlerSymbols.IsType(property.Type, TelegramHandlerSymbols.State);
 2225    }
 2226
 2227    private static bool IsPartialStateProperty(IPropertySymbol property)
 2228    {
 92229        return property
 92230            .DeclaringSyntaxReferences
 92231            .Select(static reference => reference.GetSyntax())
 92232            .OfType<PropertyDeclarationSyntax>()
 442233            .Any(static declaration => declaration.Modifiers.Any(modifier => modifier.Text == "partial"));
 2234    }
 2235
 2236    private static string ToCamelCase(string value)
 2237    {
 62238        return value.Length == 0
 62239            ? value
 62240            : char.ToLowerInvariant(value[0]) + value.Substring(1);
 2241    }
 2242
 2243    private static ImmutableArray<GeneratedParameter> BuildParameters(
 2244        IMethodSymbol method,
 2245        GeneratedHandlerKind kind,
 2246        string expectedContextType,
 2247        ITypeSymbol? callbackPayloadType,
 2248        ImmutableHashSet<string> routeValueNames)
 2249    {
 392250        ImmutableArray<GeneratedParameter>.Builder builder = ImmutableArray.CreateBuilder<GeneratedParameter>();
 2251
 1882252        foreach (IParameterSymbol parameter in method.Parameters)
 2253        {
 552254            GeneratedParameterKind parameterKind = GetParameterKind(parameter, kind, expectedContextType, callbackPayloa
 2255
 552256            builder.Add(new GeneratedParameter(
 552257                parameter.Type.ToDisplayString(FullyQualifiedFormat),
 552258                parameterKind,
 552259                parameter.Name));
 2260        }
 2261
 392262        return builder.ToImmutable();
 2263    }
 2264
 2265    private static GeneratedParameterKind GetParameterKind(
 2266        IParameterSymbol parameter,
 2267        GeneratedHandlerKind kind,
 2268        string expectedContextType,
 2269        ITypeSymbol? callbackPayloadType,
 2270        ImmutableHashSet<string> routeValueNames)
 2271    {
 552272        if (TelegramHandlerSymbols.IsType(parameter.Type, expectedContextType))
 2273        {
 392274            return GeneratedParameterKind.Context;
 2275        }
 2276
 162277        if (TelegramHandlerSymbols.IsType(parameter.Type, TelegramHandlerSymbols.CancellationToken))
 2278        {
 12279            return GeneratedParameterKind.CancellationToken;
 2280        }
 2281
 152282        if (routeValueNames.Contains(parameter.Name))
 2283        {
 42284            return GeneratedParameterKind.RouteValue;
 2285        }
 2286
 112287        if (kind == GeneratedHandlerKind.Callback &&
 112288            callbackPayloadType is not null &&
 112289            SymbolEqualityComparer.Default.Equals(parameter.Type, callbackPayloadType))
 2290        {
 32291            return GeneratedParameterKind.CallbackPayload;
 2292        }
 2293
 82294        return GeneratedParameterKind.Service;
 2295    }
 2296
 2297    private static ImmutableArray<GeneratedErrorParameter> BuildErrorParameters(IMethodSymbol method)
 2298    {
 32299        ImmutableArray<GeneratedErrorParameter>.Builder builder = ImmutableArray.CreateBuilder<GeneratedErrorParameter>(
 2300
 282301        foreach (IParameterSymbol parameter in method.Parameters)
 2302        {
 112303            builder.Add(new GeneratedErrorParameter(
 112304                parameter.Type.ToDisplayString(FullyQualifiedFormat),
 112305                GetErrorParameterKind(parameter),
 112306                parameter.Name));
 2307        }
 2308
 32309        return builder.ToImmutable();
 2310    }
 2311
 2312    private static GeneratedErrorParameterKind GetErrorParameterKind(IParameterSymbol parameter)
 2313    {
 112314        if (TelegramHandlerSymbols.IsType(parameter.Type, TelegramHandlerSymbols.TelegramErrorContext))
 2315        {
 22316            return GeneratedErrorParameterKind.ErrorContext;
 2317        }
 2318
 92319        if (TelegramHandlerSymbols.IsType(parameter.Type, TelegramHandlerSymbols.CancellationToken))
 2320        {
 22321            return GeneratedErrorParameterKind.CancellationToken;
 2322        }
 2323
 72324        if (IsExceptionType(parameter.Type))
 2325        {
 32326            return GeneratedErrorParameterKind.Exception;
 2327        }
 2328
 42329        if (IsTelegramContextType(parameter.Type))
 2330        {
 22331            return GeneratedErrorParameterKind.TelegramContext;
 2332        }
 2333
 22334        if (IsErrorRouteValueParameterType(parameter.Type))
 2335        {
 12336            return GeneratedErrorParameterKind.RouteValue;
 2337        }
 2338
 12339        return GeneratedErrorParameterKind.Service;
 2340    }
 2341
 2342    private static bool IsExceptionType(ITypeSymbol type)
 2343    {
 202344        return type is INamedTypeSymbol namedType &&
 202345               IsAssignableTo(namedType, TelegramHandlerSymbols.Exception);
 2346    }
 2347
 2348    private static bool IsTelegramContextType(ITypeSymbol type)
 2349    {
 152350        return type is INamedTypeSymbol namedType &&
 152351               IsAssignableTo(namedType, TelegramHandlerSymbols.TelegramUpdateContext);
 2352    }
 2353
 2354    private static bool IsAssignableFrom(ITypeSymbol targetType, ITypeSymbol sourceType)
 2355    {
 22356        if (sourceType is not INamedTypeSymbol source)
 2357        {
 02358            return false;
 2359        }
 2360
 42361        for (INamedTypeSymbol? current = source; current is not null; current = current.BaseType)
 2362        {
 22363            if (SymbolEqualityComparer.Default.Equals(current, targetType))
 2364            {
 22365                return true;
 2366            }
 2367        }
 2368
 02369        return false;
 2370    }
 2371
 2372    private static bool IsErrorRouteValueParameterType(ITypeSymbol type)
 2373    {
 22374        return IsRouteValueParameterType(type);
 2375    }
 2376
 2377    private static bool IsRouteValueParameterType(ITypeSymbol type)
 2378    {
 62379        return type.SpecialType is
 62380                   SpecialType.System_String or
 62381                   SpecialType.System_Int32 or
 62382                   SpecialType.System_Int64 ||
 62383               type is INamedTypeSymbol
 62384               {
 62385                   OriginalDefinition.SpecialType: SpecialType.System_Nullable_T,
 62386                   TypeArguments.Length: 1
 62387               } nullable &&
 62388               nullable.TypeArguments[0].SpecialType is
 62389                   SpecialType.System_Int32 or
 62390                   SpecialType.System_Int64;
 2391    }
 2392}

/_/src/TeleFlow.Generators/TelegramHandlerSourceGenerator.Emission.cs

#LineLine coverage
 1using System.Text;
 2using Microsoft.CodeAnalysis;
 3
 4namespace TeleFlow.Generators;
 5
 6public sealed partial class TelegramHandlerSourceGenerator
 7{
 8    private static string GenerateSource(
 9        IReadOnlyList<GeneratedHandlerMethod> handlers,
 10        IReadOnlyList<GeneratedErrorHandlerMethod> errorHandlers,
 11        IReadOnlyList<GeneratedCallbackDataPayload> callbackDataPayloads)
 12    {
 2413        StringBuilder builder = new StringBuilder();
 2414        bool hasHandlers = handlers.Count > 0 || errorHandlers.Count > 0;
 2415        bool hasCallbackDataPayloads = callbackDataPayloads.Count > 0;
 16
 2417        builder.AppendLine("// <auto-generated />");
 2418        builder.AppendLine("#nullable enable");
 19
 2420        if (hasHandlers)
 21        {
 2322            builder.AppendLine("[assembly: global::TeleFlow.Telegram.TelegramGeneratedHandlersAttribute(typeof(global::T
 23        }
 24
 2425        if (hasCallbackDataPayloads)
 26        {
 227            builder.AppendLine("[assembly: global::TeleFlow.Telegram.TelegramGeneratedCallbackDataCodecsAttribute(typeof
 28        }
 29
 2430        builder.AppendLine();
 2431        builder.AppendLine("namespace TeleFlow.Generated;");
 2432        builder.AppendLine();
 2433        builder.Append("internal sealed class TelegramGeneratedHandlersRegistrar : ");
 2434        if (hasHandlers)
 35        {
 2336            builder.Append("global::TeleFlow.Telegram.ITelegramGeneratedHandlerRegistrar");
 37        }
 38
 2439        if (hasHandlers && hasCallbackDataPayloads)
 40        {
 141            builder.Append(", ");
 42        }
 43
 2444        if (hasCallbackDataPayloads)
 45        {
 246            builder.Append("global::TeleFlow.Telegram.ITelegramGeneratedCallbackDataCodecRegistrar");
 47        }
 48
 2449        builder.AppendLine();
 2450        builder.AppendLine("{");
 51
 2452        if (hasHandlers)
 53        {
 2354            builder.AppendLine("    public void Register(global::TeleFlow.Telegram.ITelegramGeneratedHandlerRegistry reg
 2355            builder.AppendLine("    {");
 56
 2357            int descriptorIndex = 0;
 58
 12459            for (int handlerIndex = 0; handlerIndex < handlers.Count; handlerIndex++)
 60            {
 16061                foreach (GeneratedRoute route in handlers[handlerIndex].Routes)
 62                {
 4163                    AppendRegistration(builder, handlers[handlerIndex], route, handlerIndex, descriptorIndex);
 4164                    descriptorIndex++;
 65                }
 66            }
 67
 2368            int errorDescriptorIndex = 0;
 69
 5270            for (int handlerIndex = 0; handlerIndex < errorHandlers.Count; handlerIndex++)
 71            {
 1272                foreach (string? exceptionType in errorHandlers[handlerIndex].ExceptionTypes)
 73                {
 374                    AppendErrorRegistration(
 375                        builder,
 376                        errorHandlers[handlerIndex],
 377                        exceptionType,
 378                        handlerIndex,
 379                        errorDescriptorIndex);
 380                    errorDescriptorIndex++;
 81                }
 82            }
 83
 2384            builder.AppendLine("    }");
 2385            builder.AppendLine();
 86        }
 87
 2488        if (hasCallbackDataPayloads)
 89        {
 290            AppendCallbackDataCodecRegistration(builder, callbackDataPayloads);
 91        }
 92
 12693        for (int index = 0; index < handlers.Count; index++)
 94        {
 3995            AppendInvoker(builder, handlers[index], index);
 96        }
 97
 5498        for (int index = 0; index < errorHandlers.Count; index++)
 99        {
 3100            AppendErrorInvoker(builder, errorHandlers[index], index);
 101        }
 102
 52103        for (int index = 0; index < callbackDataPayloads.Count; index++)
 104        {
 2105            AppendCallbackDataCodec(builder, callbackDataPayloads[index], index);
 106        }
 107
 24108        if (hasCallbackDataPayloads)
 109        {
 2110            AppendCallbackDataHelpers(builder);
 111        }
 112
 24113        builder.AppendLine("}");
 114
 24115        return builder.ToString();
 116    }
 117
 118    private static void AppendCallbackDataCodecRegistration(
 119        StringBuilder builder,
 120        IReadOnlyList<GeneratedCallbackDataPayload> callbackDataPayloads)
 121    {
 2122        builder.AppendLine("    public void RegisterCallbackDataCodecs(global::TeleFlow.Telegram.ITelegramGeneratedCallb
 2123        builder.AppendLine("    {");
 124
 8125        for (int index = 0; index < callbackDataPayloads.Count; index++)
 126        {
 2127            GeneratedCallbackDataPayload payload = callbackDataPayloads[index];
 128
 2129            builder.AppendLine("        registry.RegisterCallbackDataCodec(new global::TeleFlow.Telegram.TelegramGenerat
 2130            builder.AppendLine($"            typeof({payload.TypeName}),");
 2131            builder.AppendLine($"            {ToLiteral(payload.Prefix)},");
 2132            builder.AppendLine($"            PackCallbackData_{index},");
 2133            builder.AppendLine($"            MatchesCallbackData_{index},");
 2134            builder.AppendLine($"            UnpackCallbackData_{index}));");
 135        }
 136
 2137        builder.AppendLine("    }");
 2138        builder.AppendLine();
 2139    }
 140
 141    private static string GenerateStateGroupsSource(IReadOnlyList<GeneratedStateGroup> groups)
 142    {
 4143        StringBuilder builder = new StringBuilder();
 144
 4145        builder.AppendLine("// <auto-generated />");
 4146        builder.AppendLine("#nullable enable");
 4147        builder.AppendLine();
 148
 20149        foreach (IGrouping<string?, GeneratedStateGroup> namespaceGroup in groups.GroupBy(static group => group.Namespac
 150        {
 4151            if (!string.IsNullOrWhiteSpace(namespaceGroup.Key))
 152            {
 4153                builder.AppendLine($"namespace {namespaceGroup.Key};");
 4154                builder.AppendLine();
 155            }
 156
 16157            foreach (GeneratedStateGroup group in namespaceGroup)
 158            {
 4159                builder.Append(group.Accessibility);
 4160                builder.Append(' ');
 161
 4162                if (group.IsStatic)
 163                {
 0164                    builder.Append("static ");
 165                }
 166
 4167                builder.Append("partial class ");
 4168                builder.AppendLine(group.TypeName);
 4169                builder.AppendLine("{");
 170
 20171                foreach (GeneratedStateProperty state in group.States)
 172                {
 6173                    builder.Append("    public static partial global::TeleFlow.Framework.States.State ");
 6174                    builder.Append(state.Name);
 6175                    builder.Append(" => global::TeleFlow.Framework.States.State.Create(");
 6176                    builder.Append(ToLiteral($"{group.Prefix}:{state.Segment}"));
 6177                    builder.AppendLine(");");
 178                }
 179
 4180                builder.AppendLine("}");
 4181                builder.AppendLine();
 182            }
 183        }
 184
 4185        return builder.ToString();
 186    }
 187
 188    private static void AppendRegistration(
 189        StringBuilder builder,
 190        GeneratedHandlerMethod handler,
 191        GeneratedRoute route,
 192        int invokerIndex,
 193        int descriptorIndex)
 194    {
 41195        builder.AppendLine("        registry.RegisterHandler(new global::TeleFlow.Telegram.TelegramGeneratedHandlerDescr
 41196        builder.AppendLine($"            typeof({handler.HandlerTypeName}),");
 41197        builder.AppendLine($"            {ToLiteral(handler.MethodName)},");
 41198        builder.AppendLine($"            global::TeleFlow.Telegram.TelegramGeneratedHandlerKind.{route.Kind},");
 41199        builder.AppendLine($"            global::TeleFlow.Telegram.TelegramGeneratedRouteKind.{route.RouteKind},");
 41200        builder.AppendLine($"            {ToNullableLiteral(route.Pattern)},");
 41201        builder.AppendLine("            new string[]");
 41202        builder.AppendLine("            {");
 203
 168204        foreach (string prefix in route.CommandPrefixes)
 205        {
 43206            builder.AppendLine($"                {ToLiteral(prefix)},");
 207        }
 208
 41209        builder.AppendLine("            },");
 41210        builder.AppendLine($"            {ToBoolLiteral(route.AllowSpaceAfterPrefix)},");
 41211        builder.AppendLine($"            {ToBoolLiteral(route.IgnoreCase)},");
 41212        builder.AppendLine($"            {descriptorIndex},");
 41213        builder.AppendLine($"            {ToNullableLiteral(handler.ModuleName)},");
 41214        builder.AppendLine($"            {ToNullableLiteral(route.Command)},");
 41215        builder.AppendLine($"            {ToTypeOfNullable(handler.CallbackPayloadType)},");
 41216        builder.AppendLine("            new global::TeleFlow.Telegram.TelegramGeneratedTextFilterDescriptor[]");
 41217        builder.AppendLine("            {");
 218
 86219        foreach (GeneratedTextFilter filter in route.TextFilters)
 220        {
 2221            builder.AppendLine(
 2222                $"                new global::TeleFlow.Telegram.TelegramGeneratedTextFilterDescriptor({ToLiteral(filter.
 223        }
 224
 41225        builder.AppendLine("            },");
 41226        builder.AppendLine("            new global::TeleFlow.Telegram.TelegramGeneratedFilterDescriptor[]");
 41227        builder.AppendLine("            {");
 228
 134229        foreach (GeneratedFilter filter in route.Filters)
 230        {
 26231            AppendFilter(builder, filter);
 232        }
 233
 41234        builder.AppendLine("            },");
 41235        builder.AppendLine("            new global::TeleFlow.Telegram.TelegramGeneratedChatMemberTransitionDescriptor[]"
 41236        builder.AppendLine("            {");
 237
 96238        foreach (GeneratedChatMemberTransition transition in route.ChatMemberTransitions)
 239        {
 7240            builder.AppendLine(
 7241                $"                new global::TeleFlow.Telegram.TelegramGeneratedChatMemberTransitionDescriptor((global:
 242        }
 243
 41244        builder.AppendLine("            },");
 41245        builder.AppendLine("            new global::TeleFlow.Telegram.TelegramGeneratedRoleRequirementDescriptor[]");
 41246        builder.AppendLine("            {");
 247
 86248        foreach (int requirement in route.RoleRequirements)
 249        {
 2250            builder.AppendLine(
 2251                $"                new global::TeleFlow.Telegram.TelegramGeneratedRoleRequirementDescriptor((global::Tele
 252        }
 253
 41254        builder.AppendLine("            },");
 41255        builder.AppendLine("            new string[]");
 41256        builder.AppendLine("            {");
 257
 94258        foreach (string state in handler.States)
 259        {
 6260            builder.AppendLine($"                {ToLiteral(state)},");
 261        }
 262
 41263        builder.AppendLine("            },");
 41264        builder.AppendLine("            new global::TeleFlow.Telegram.TelegramGeneratedHandlerParameterDescriptor[]");
 41265        builder.AppendLine("            {");
 266
 198267        foreach (GeneratedParameter parameter in handler.Parameters)
 268        {
 58269            builder.AppendLine(
 58270                $"                new global::TeleFlow.Telegram.TelegramGeneratedHandlerParameterDescriptor(typeof({para
 271        }
 272
 41273        builder.AppendLine("            },");
 41274        builder.AppendLine($"            Invoke_{invokerIndex},");
 41275        builder.AppendLine($"            {ToNullableLiteral(handler.SceneName)},");
 41276        builder.AppendLine("            new global::TeleFlow.Telegram.TelegramGeneratedRouteValueDescriptor[]");
 41277        builder.AppendLine("            {");
 278
 46279        if (route.RouteValues.Values.Any(static value => value.Constraint is not null))
 280        {
 20281            foreach (KeyValuePair<string, GeneratedRouteValue> routeValue in route.RouteValues.OrderBy(static value => v
 282            {
 4283                builder.AppendLine(
 4284                    $"                new global::TeleFlow.Telegram.TelegramGeneratedRouteValueDescriptor({ToLiteral(rou
 285            }
 286        }
 287
 41288        builder.AppendLine("            },");
 41289        AppendAutoAnswerCallback(builder, handler.AutoAnswerCallback);
 290
 41291        if (route.PrefixMode == CommandPrefixModeRequired)
 292        {
 40293            builder.AppendLine("            ));");
 294        }
 295        else
 296        {
 1297            builder.AppendLine(",");
 1298            builder.AppendLine($"            prefixMode: {ToCommandPrefixModeLiteral(route.PrefixMode)}));");
 299        }
 1300    }
 301
 302    private static void AppendAutoAnswerCallback(
 303        StringBuilder builder,
 304        GeneratedAutoAnswerCallback? autoAnswerCallback)
 305    {
 41306        if (autoAnswerCallback is null)
 307        {
 40308            builder.AppendLine("            null");
 40309            return;
 310        }
 311
 1312        builder.AppendLine("            new global::TeleFlow.Telegram.TelegramGeneratedAutoAnswerCallbackDescriptor(");
 1313        builder.AppendLine($"                enabled: {ToBoolLiteral(autoAnswerCallback.Enabled)},");
 1314        builder.AppendLine($"                text: {ToNullableLiteral(autoAnswerCallback.Text)},");
 1315        builder.AppendLine($"                showAlert: {ToBoolLiteral(autoAnswerCallback.ShowAlert)})");
 1316    }
 317
 318    private static void AppendFilter(
 319        StringBuilder builder,
 320        GeneratedFilter filter)
 321    {
 26322        if (filter.CustomTypeName is not null)
 323        {
 3324            if (filter.CustomAttributeExpression is null)
 325            {
 2326                builder.AppendLine(
 2327                    $"                new global::TeleFlow.Telegram.TelegramGeneratedFilterDescriptor(typeof({filter.Cus
 328            }
 329            else
 330            {
 1331                builder.AppendLine(
 1332                    $"                new global::TeleFlow.Telegram.TelegramGeneratedFilterDescriptor(typeof({filter.Cus
 333            }
 334
 1335            return;
 336        }
 337
 23338        builder.AppendLine("                new global::TeleFlow.Telegram.TelegramGeneratedFilterDescriptor(");
 23339        builder.AppendLine($"                    global::TeleFlow.Telegram.TelegramGeneratedFilterKind.{filter.Kind},");
 23340        builder.AppendLine("                    new string[]");
 23341        builder.AppendLine("                    {");
 342
 66343        foreach (string value in filter.StringValues)
 344        {
 10345            builder.AppendLine($"                        {ToLiteral(value)},");
 346        }
 347
 23348        builder.AppendLine("                    },");
 23349        builder.AppendLine("                    new long[]");
 23350        builder.AppendLine("                    {");
 351
 56352        foreach (long value in filter.LongValues)
 353        {
 5354            builder.AppendLine($"                        {value}L,");
 355        }
 356
 23357        builder.AppendLine("                    }),");
 23358    }
 359
 360    private static void AppendErrorRegistration(
 361        StringBuilder builder,
 362        GeneratedErrorHandlerMethod handler,
 363        string? exceptionType,
 364        int invokerIndex,
 365        int descriptorIndex)
 366    {
 3367        builder.AppendLine("        registry.RegisterErrorHandler(new global::TeleFlow.Telegram.TelegramGeneratedErrorHa
 3368        builder.AppendLine($"            typeof({handler.HandlerTypeName}),");
 3369        builder.AppendLine($"            {ToLiteral(handler.MethodName)},");
 3370        builder.AppendLine($"            exceptionType: {ToTypeOfNullable(exceptionType)},");
 3371        builder.AppendLine($"            telegramContextType: {ToTypeOfNullable(handler.TelegramContextType)},");
 3372        builder.AppendLine($"            registrationOrder: {descriptorIndex},");
 3373        builder.AppendLine($"            moduleName: {ToNullableLiteral(handler.ModuleName)},");
 3374        builder.AppendLine("            parameters: new global::TeleFlow.Telegram.TelegramGeneratedErrorHandlerParameter
 3375        builder.AppendLine("            {");
 376
 28377        foreach (GeneratedErrorParameter parameter in handler.Parameters)
 378        {
 11379            builder.AppendLine(
 11380                $"                new global::TeleFlow.Telegram.TelegramGeneratedErrorHandlerParameterDescriptor(typeof(
 381        }
 382
 3383        builder.AppendLine("            },");
 3384        builder.AppendLine($"            invoker: InvokeError_{invokerIndex}));");
 3385    }
 386
 387    private static void AppendCallbackDataCodec(
 388        StringBuilder builder,
 389        GeneratedCallbackDataPayload payload,
 390        int index)
 391    {
 2392        builder.AppendLine($"    private static string PackCallbackData_{index}(object payload)");
 2393        builder.AppendLine("    {");
 2394        builder.AppendLine($"        var value = ({payload.TypeName})payload;");
 2395        builder.AppendLine("        return string.Concat(");
 2396        builder.AppendLine($"            {ToLiteral(payload.Prefix)}");
 397
 20398        foreach (GeneratedCallbackDataField field in payload.Fields)
 399        {
 8400            builder.AppendLine("            ,");
 8401            builder.Append("            \":\", ");
 8402            builder.Append(GetCallbackDataPackExpression(field));
 8403            builder.AppendLine();
 404        }
 405
 2406        builder.AppendLine("        );");
 2407        builder.AppendLine("    }");
 2408        builder.AppendLine();
 409
 2410        builder.AppendLine($"    private static bool MatchesCallbackData_{index}(string serializedPayload)");
 2411        builder.AppendLine("    {");
 2412        builder.AppendLine($"        return MatchesCallbackData(serializedPayload, {ToLiteral(payload.Prefix)}, {payload
 2413        builder.AppendLine("    }");
 2414        builder.AppendLine();
 415
 2416        builder.AppendLine($"    private static object UnpackCallbackData_{index}(string serializedPayload)");
 2417        builder.AppendLine("    {");
 2418        builder.AppendLine(
 2419            $"        var parts = SplitCallbackData(serializedPayload, {ToLiteral(payload.Prefix)}, {payload.Fields.Leng
 420
 2421        if (payload.UsesConstructor)
 422        {
 2423            builder.Append($"        return new {payload.TypeName}(");
 424
 20425            for (int fieldIndex = 0; fieldIndex < payload.Fields.Length; fieldIndex++)
 426            {
 8427                if (fieldIndex > 0)
 428                {
 6429                    builder.Append(", ");
 430                }
 431
 8432                builder.Append(GetCallbackDataUnpackExpression(payload, payload.Fields[fieldIndex], fieldIndex + 1));
 433            }
 434
 2435            builder.AppendLine(");");
 436        }
 437        else
 438        {
 0439            builder.AppendLine($"        return new {payload.TypeName}");
 0440            builder.AppendLine("        {");
 441
 0442            for (int fieldIndex = 0; fieldIndex < payload.Fields.Length; fieldIndex++)
 443            {
 0444                GeneratedCallbackDataField field = payload.Fields[fieldIndex];
 0445                builder.AppendLine($"            {field.PropertyName} = {GetCallbackDataUnpackExpression(payload, field,
 446            }
 447
 0448            builder.AppendLine("        };");
 449        }
 450
 2451        builder.AppendLine("    }");
 2452        builder.AppendLine();
 2453    }
 454
 455    private static string GetCallbackDataPackExpression(GeneratedCallbackDataField field)
 456    {
 8457        string property = $"value.{field.PropertyName}";
 458
 8459        return field.Kind switch
 8460        {
 2461            GeneratedCallbackDataFieldKind.String => $"EscapeCallbackDataField({property})",
 8462            GeneratedCallbackDataFieldKind.Int32 or
 2463                GeneratedCallbackDataFieldKind.Int64 => $"{property}.ToString(global::System.Globalization.CultureInfo.I
 2464            GeneratedCallbackDataFieldKind.Boolean => $"{property} ? \"true\" : \"false\"",
 2465            GeneratedCallbackDataFieldKind.Enum => $"EscapeCallbackDataField({property}.ToString())",
 0466            _ => throw new InvalidOperationException($"Unsupported generated callback data field kind '{field.Kind}'.")
 8467        };
 468    }
 469
 470    private static string GetCallbackDataUnpackExpression(
 471        GeneratedCallbackDataPayload payload,
 472        GeneratedCallbackDataField field,
 473        int partIndex)
 474    {
 8475        string value = $"UnescapeCallbackDataField(parts[{partIndex}])";
 476
 8477        return field.Kind switch
 8478        {
 2479            GeneratedCallbackDataFieldKind.String => value,
 0480            GeneratedCallbackDataFieldKind.Int32 => $"global::System.Int32.Parse({value}, global::System.Globalization.C
 2481            GeneratedCallbackDataFieldKind.Int64 => $"global::System.Int64.Parse({value}, global::System.Globalization.C
 2482            GeneratedCallbackDataFieldKind.Boolean => $"global::System.Boolean.Parse({value})",
 2483            GeneratedCallbackDataFieldKind.Enum => $"ParseCallbackDataEnum<{field.TypeName}>({value}, {ToLiteral(field.P
 0484            _ => throw new InvalidOperationException($"Unsupported generated callback data field kind '{field.Kind}'.")
 8485        };
 486    }
 487
 488    private static void AppendCallbackDataHelpers(StringBuilder builder)
 489    {
 2490        builder.AppendLine("    private static bool MatchesCallbackData(string serializedPayload, string prefix, int fie
 2491        builder.AppendLine("    {");
 2492        builder.AppendLine("        if (!serializedPayload.StartsWith(prefix, global::System.StringComparison.Ordinal))"
 2493        builder.AppendLine("        {");
 2494        builder.AppendLine("            return false;");
 2495        builder.AppendLine("        }");
 2496        builder.AppendLine();
 2497        builder.AppendLine("        if (fieldCount == 0)");
 2498        builder.AppendLine("        {");
 2499        builder.AppendLine("            return serializedPayload.Length == prefix.Length;");
 2500        builder.AppendLine("        }");
 2501        builder.AppendLine();
 2502        builder.AppendLine("        if (serializedPayload.Length <= prefix.Length || serializedPayload[prefix.Length] !=
 2503        builder.AppendLine("        {");
 2504        builder.AppendLine("            return false;");
 2505        builder.AppendLine("        }");
 2506        builder.AppendLine();
 2507        builder.AppendLine("        var separatorCount = 0;");
 2508        builder.AppendLine();
 2509        builder.AppendLine("        for (var index = prefix.Length; index < serializedPayload.Length; index++)");
 2510        builder.AppendLine("        {");
 2511        builder.AppendLine("            if (serializedPayload[index] == ':')");
 2512        builder.AppendLine("            {");
 2513        builder.AppendLine("                separatorCount++;");
 2514        builder.AppendLine("            }");
 2515        builder.AppendLine("        }");
 2516        builder.AppendLine();
 2517        builder.AppendLine("        return separatorCount == fieldCount;");
 2518        builder.AppendLine("    }");
 2519        builder.AppendLine();
 520
 2521        builder.AppendLine("    private static string[] SplitCallbackData(string serializedPayload, string prefix, int f
 2522        builder.AppendLine("    {");
 2523        builder.AppendLine("        if (!MatchesCallbackData(serializedPayload, prefix, fieldCount))");
 2524        builder.AppendLine("        {");
 2525        builder.AppendLine("            throw new global::System.Text.Json.JsonException($\"Telegram callback data does 
 2526        builder.AppendLine("        }");
 2527        builder.AppendLine();
 2528        builder.AppendLine("        return serializedPayload.Split(':');");
 2529        builder.AppendLine("    }");
 2530        builder.AppendLine();
 531
 2532        builder.AppendLine("    private static string EscapeCallbackDataField(string? value)");
 2533        builder.AppendLine("    {");
 2534        builder.AppendLine("        if (value is null)");
 2535        builder.AppendLine("        {");
 2536        builder.AppendLine("            throw new global::System.InvalidOperationException(\"Compact callback data field
 2537        builder.AppendLine("        }");
 2538        builder.AppendLine();
 2539        builder.AppendLine("        return value");
 2540        builder.AppendLine("            .Replace(\"%\", \"%25\", global::System.StringComparison.Ordinal)");
 2541        builder.AppendLine("            .Replace(\":\", \"%3A\", global::System.StringComparison.Ordinal);");
 2542        builder.AppendLine("    }");
 2543        builder.AppendLine();
 544
 2545        builder.AppendLine("    private static string UnescapeCallbackDataField(string value)");
 2546        builder.AppendLine("    {");
 2547        builder.AppendLine("        return value");
 2548        builder.AppendLine("            .Replace(\"%3A\", \":\", global::System.StringComparison.Ordinal)");
 2549        builder.AppendLine("            .Replace(\"%25\", \"%\", global::System.StringComparison.Ordinal);");
 2550        builder.AppendLine("    }");
 2551        builder.AppendLine();
 552
 2553        builder.AppendLine("    private static TEnum ParseCallbackDataEnum<TEnum>(string value, string fieldName, string
 2554        builder.AppendLine("        where TEnum : struct, global::System.Enum");
 2555        builder.AppendLine("    {");
 2556        builder.AppendLine("        if (global::System.Enum.TryParse<TEnum>(value, ignoreCase: false, out var result))")
 2557        builder.AppendLine("        {");
 2558        builder.AppendLine("            return result;");
 2559        builder.AppendLine("        }");
 2560        builder.AppendLine();
 2561        builder.AppendLine("        throw new global::System.Text.Json.JsonException($\"Telegram callback data field '{f
 2562        builder.AppendLine("    }");
 2563        builder.AppendLine();
 2564    }
 565
 566    private static void AppendInvoker(StringBuilder builder, GeneratedHandlerMethod handler, int index)
 567    {
 39568        builder.AppendLine($"    private static async global::System.Threading.Tasks.ValueTask Invoke_{index}(");
 39569        builder.AppendLine("        global::System.IServiceProvider services,");
 39570        builder.AppendLine("        object?[] arguments,");
 39571        builder.AppendLine("        global::System.Threading.CancellationToken cancellationToken)");
 39572        builder.AppendLine("    {");
 39573        builder.AppendLine(
 39574            $"        var handler = ({handler.HandlerTypeName})(services.GetService(typeof({handler.HandlerTypeName})) ?
 39575        builder.Append("        await handler.");
 39576        builder.Append(handler.MethodName);
 39577        builder.Append('(');
 578
 188579        for (int parameterIndex = 0; parameterIndex < handler.Parameters.Length; parameterIndex++)
 580        {
 55581            if (parameterIndex > 0)
 582            {
 16583                builder.Append(", ");
 584            }
 585
 55586            GeneratedParameter parameter = handler.Parameters[parameterIndex];
 55587            builder.Append('(');
 55588            builder.Append(parameter.TypeName);
 55589            builder.Append($")arguments[{parameterIndex}]!");
 590        }
 591
 39592        builder.AppendLine(").ConfigureAwait(false);");
 39593        builder.AppendLine("    }");
 39594        builder.AppendLine();
 39595    }
 596
 597    private static void AppendErrorInvoker(StringBuilder builder, GeneratedErrorHandlerMethod handler, int index)
 598    {
 3599        string asyncKeyword = handler.ReturnKind == GeneratedErrorReturnKind.Sync ? string.Empty : "async ";
 600
 3601        builder.AppendLine($"    private static {asyncKeyword}global::System.Threading.Tasks.ValueTask<global::TeleFlow.
 3602        builder.AppendLine("        global::System.IServiceProvider services,");
 3603        builder.AppendLine("        object?[] arguments,");
 3604        builder.AppendLine("        global::System.Threading.CancellationToken cancellationToken)");
 3605        builder.AppendLine("    {");
 3606        builder.AppendLine(
 3607            $"        var handler = ({handler.HandlerTypeName})(services.GetService(typeof({handler.HandlerTypeName})) ?
 608
 3609        if (handler.ReturnKind == GeneratedErrorReturnKind.Sync)
 610        {
 1611            builder.Append("        return new global::System.Threading.Tasks.ValueTask<global::TeleFlow.Telegram.Telegr
 1612            AppendErrorInvocationArguments(builder, handler);
 1613            builder.AppendLine(");");
 614        }
 615        else
 616        {
 2617            builder.Append("        return await handler.");
 2618            AppendErrorInvocationArguments(builder, handler);
 2619            builder.AppendLine(".ConfigureAwait(false);");
 620        }
 621
 3622        builder.AppendLine("    }");
 3623        builder.AppendLine();
 3624    }
 625
 626    private static void AppendErrorInvocationArguments(
 627        StringBuilder builder,
 628        GeneratedErrorHandlerMethod handler)
 629    {
 3630        builder.Append(handler.MethodName);
 3631        builder.Append('(');
 632
 28633        for (int parameterIndex = 0; parameterIndex < handler.Parameters.Length; parameterIndex++)
 634        {
 11635            if (parameterIndex > 0)
 636            {
 8637                builder.Append(", ");
 638            }
 639
 11640            GeneratedErrorParameter parameter = handler.Parameters[parameterIndex];
 11641            builder.Append('(');
 11642            builder.Append(parameter.TypeName);
 11643            builder.Append($")arguments[{parameterIndex}]!");
 644        }
 645
 3646        builder.Append(')');
 3647    }
 648
 649    private static string ToNullableLiteral(string? value)
 650    {
 168651        return value is null ? "null" : ToLiteral(value);
 652    }
 653
 654    private static string ToTypeOfNullable(string? typeName)
 655    {
 47656        return typeName is null ? "null" : $"typeof({typeName})";
 657    }
 658
 659    private static string ToLiteral(string value)
 660    {
 276661        StringBuilder builder = new StringBuilder(value.Length + 2);
 276662        builder.Append('"');
 663
 11594664        foreach (char character in value)
 665        {
 5521666            builder.Append(character switch
 5521667            {
 1668                '\\' => "\\\\",
 0669                '"' => "\\\"",
 0670                '\r' => "\\r",
 0671                '\n' => "\\n",
 0672                '\t' => "\\t",
 5520673                _ => character.ToString()
 5521674            });
 675        }
 676
 276677        builder.Append('"');
 276678        return builder.ToString();
 679    }
 680
 681    private static string ToBoolLiteral(bool value)
 682    {
 91683        return value ? "true" : "false";
 684    }
 685
 686    private static string ToCommandPrefixModeLiteral(int value)
 687    {
 1688        return value switch
 1689        {
 0690            CommandPrefixModeRequired => "global::TeleFlow.Annotations.CommandPrefixMode.Required",
 1691            CommandPrefixModeOptional => "global::TeleFlow.Annotations.CommandPrefixMode.Optional",
 0692            CommandPrefixModeNoPrefix => "global::TeleFlow.Annotations.CommandPrefixMode.NoPrefix",
 0693            _ => $"(global::TeleFlow.Annotations.CommandPrefixMode){value}"
 1694        };
 695    }
 696
 697    private static string ToAttributeCreationExpression(AttributeData attribute)
 698    {
 1699        if (attribute.AttributeClass is null)
 700        {
 0701            return "null!";
 702        }
 703
 1704        StringBuilder builder = new StringBuilder();
 1705        builder.Append("new ");
 1706        builder.Append(attribute.AttributeClass.ToDisplayString(FullyQualifiedFormat));
 1707        builder.Append('(');
 708
 4709        for (int index = 0; index < attribute.ConstructorArguments.Length; index++)
 710        {
 1711            if (index > 0)
 712            {
 0713                builder.Append(", ");
 714            }
 715
 1716            builder.Append(ToAttributeArgumentExpression(attribute.ConstructorArguments[index]));
 717        }
 718
 1719        builder.Append(')');
 720
 1721        if (attribute.NamedArguments.Length > 0)
 722        {
 1723            builder.Append(" { ");
 724
 4725            for (int index = 0; index < attribute.NamedArguments.Length; index++)
 726            {
 1727                if (index > 0)
 728                {
 0729                    builder.Append(", ");
 730                }
 731
 1732                KeyValuePair<string, TypedConstant> argument = attribute.NamedArguments[index];
 1733                builder.Append(argument.Key);
 1734                builder.Append(" = ");
 1735                builder.Append(ToAttributeArgumentExpression(argument.Value));
 736            }
 737
 1738            builder.Append(" }");
 739        }
 740
 1741        return builder.ToString();
 742    }
 743
 744    private static string ToAttributeArgumentExpression(TypedConstant constant)
 745    {
 2746        if (constant.IsNull)
 747        {
 0748            return "null";
 749        }
 750
 2751        return constant.Kind switch
 2752        {
 2753            TypedConstantKind.Primitive => ToPrimitiveLiteral(constant.Value, constant.Type),
 0754            TypedConstantKind.Enum => ToEnumLiteral(constant),
 0755            TypedConstantKind.Type => constant.Value is ITypeSymbol type
 0756                ? $"typeof({type.ToDisplayString(FullyQualifiedFormat)})"
 0757                : "null",
 0758            TypedConstantKind.Array => ToArrayLiteral(constant),
 0759            _ => "null"
 2760        };
 761    }
 762
 763    private static string ToPrimitiveLiteral(
 764        object? value,
 765        ITypeSymbol? type)
 766    {
 2767        return value switch
 2768        {
 1769            string stringValue => ToLiteral(stringValue),
 0770            char charValue => ToCharLiteral(charValue),
 1771            bool boolValue => ToBoolLiteral(boolValue),
 0772            byte byteValue => byteValue.ToString(System.Globalization.CultureInfo.InvariantCulture),
 0773            sbyte sbyteValue => sbyteValue.ToString(System.Globalization.CultureInfo.InvariantCulture),
 0774            short shortValue => shortValue.ToString(System.Globalization.CultureInfo.InvariantCulture),
 0775            ushort ushortValue => ushortValue.ToString(System.Globalization.CultureInfo.InvariantCulture),
 0776            int intValue => intValue.ToString(System.Globalization.CultureInfo.InvariantCulture),
 0777            uint uintValue => uintValue.ToString(System.Globalization.CultureInfo.InvariantCulture) + "U",
 0778            long longValue => longValue.ToString(System.Globalization.CultureInfo.InvariantCulture) + "L",
 0779            ulong ulongValue => ulongValue.ToString(System.Globalization.CultureInfo.InvariantCulture) + "UL",
 0780            float floatValue => floatValue.ToString("R", System.Globalization.CultureInfo.InvariantCulture) + "F",
 0781            double doubleValue => doubleValue.ToString("R", System.Globalization.CultureInfo.InvariantCulture) + "D",
 0782            _ when type?.SpecialType == SpecialType.System_Object => "null",
 0783            _ => value?.ToString() ?? "null"
 2784        };
 785    }
 786
 787    private static string ToCharLiteral(char value)
 788    {
 0789        string escaped = value switch
 0790        {
 0791            '\'' => "\\'",
 0792            '\\' => "\\\\",
 0793            '\r' => "\\r",
 0794            '\n' => "\\n",
 0795            '\t' => "\\t",
 0796            _ => value.ToString()
 0797        };
 798
 0799        return $"'{escaped}'";
 800    }
 801
 802    private static string ToEnumLiteral(TypedConstant constant)
 803    {
 0804        var enumType = constant.Type?.ToDisplayString(FullyQualifiedFormat) ?? "int";
 0805        var value = constant.Value switch
 0806        {
 0807            ulong ulongValue => ulongValue.ToString(System.Globalization.CultureInfo.InvariantCulture) + "UL",
 0808            long longValue => longValue.ToString(System.Globalization.CultureInfo.InvariantCulture) + "L",
 0809            uint uintValue => uintValue.ToString(System.Globalization.CultureInfo.InvariantCulture) + "U",
 0810            _ => Convert.ToString(constant.Value, System.Globalization.CultureInfo.InvariantCulture) ?? "0"
 0811        };
 812
 0813        return $"({enumType}){value}";
 814    }
 815
 816    private static string ToArrayLiteral(TypedConstant constant)
 817    {
 0818        var elementType = constant.Type is IArrayTypeSymbol arrayType
 0819            ? arrayType.ElementType.ToDisplayString(FullyQualifiedFormat)
 0820            : "object";
 0821        StringBuilder builder = new StringBuilder();
 822
 0823        builder.Append("new ");
 0824        builder.Append(elementType);
 0825        builder.Append("[] { ");
 826
 0827        for (int index = 0; index < constant.Values.Length; index++)
 828        {
 0829            if (index > 0)
 830            {
 0831                builder.Append(", ");
 832            }
 833
 0834            builder.Append(ToAttributeArgumentExpression(constant.Values[index]));
 835        }
 836
 0837        builder.Append(" }");
 838
 0839        return builder.ToString();
 840    }
 841}

/_/src/TeleFlow.Generators/TelegramHandlerSourceGenerator.Models.cs

#LineLine coverage
 1using System.Collections.Immutable;
 2
 3namespace TeleFlow.Generators;
 4
 5public sealed partial class TelegramHandlerSourceGenerator
 6{
 7    private const int CommandPrefixModeRequired = 0;
 8    private const int CommandPrefixModeOptional = 1;
 9    private const int CommandPrefixModeNoPrefix = 2;
 10
 11    private enum GeneratedHandlerKind
 12    {
 13        Command,
 14        Message,
 15        Callback,
 16        ChatMember
 17    }
 18
 19    private enum GeneratedParameterKind
 20    {
 21        Context,
 22        CallbackPayload,
 23        RouteValue,
 24        Service,
 25        CancellationToken
 26    }
 27
 28    private enum GeneratedErrorParameterKind
 29    {
 30        ErrorContext,
 31        TelegramContext,
 32        Exception,
 33        RouteValue,
 34        Service,
 35        CancellationToken
 36    }
 37
 38    private enum GeneratedErrorReturnKind
 39    {
 40        Sync,
 41        Task,
 42        ValueTask
 43    }
 44
 45    private enum GeneratedCallbackDataFieldKind
 46    {
 47        String,
 48        Int32,
 49        Int64,
 50        Boolean,
 51        Enum
 52    }
 53
 54    private enum GeneratedRouteKind
 55    {
 56        MessageAny,
 57        TextExact,
 58        CommandExact,
 59        TextTemplate,
 60        CommandTemplate,
 61        TextRegex,
 62        CommandRegex,
 63        Callback,
 64        ChatMemberUpdated,
 65        MyChatMemberUpdated
 66    }
 67
 3968    private sealed record GeneratedHandlerMethod(
 3969        string SignatureKey,
 3970        string HandlerTypeName,
 3971        string HandlerTypeMetadataName,
 3972        string MethodName,
 3973        string? ModuleName,
 3974        string? SceneName,
 3975        string? CallbackPayloadType,
 3976        GeneratedAutoAnswerCallback? AutoAnswerCallback,
 3977        ImmutableArray<GeneratedRoute> Routes,
 3978        ImmutableArray<string> States,
 3979        ImmutableArray<GeneratedParameter> Parameters,
 3980        string SourcePath,
 3981        int SourceSpanStart);
 82
 383    private sealed record GeneratedErrorHandlerMethod(
 384        string SignatureKey,
 385        string HandlerTypeName,
 386        string HandlerTypeMetadataName,
 387        string MethodName,
 388        string? ModuleName,
 389        ImmutableArray<string?> ExceptionTypes,
 390        string? TelegramContextType,
 391        GeneratedErrorReturnKind ReturnKind,
 392        ImmutableArray<GeneratedErrorParameter> Parameters,
 393        string SourcePath,
 394        int SourceSpanStart);
 95
 296    private sealed record GeneratedCallbackDataPayload(
 297        string TypeName,
 298        string TypeMetadataName,
 299        string Prefix,
 2100        bool UsesConstructor,
 2101        ImmutableArray<GeneratedCallbackDataField> Fields,
 2102        string SourcePath,
 2103        int SourceSpanStart);
 104
 43105    private sealed record GeneratedRoute(
 43106        GeneratedHandlerKind Kind,
 43107        GeneratedRouteKind RouteKind,
 43108        string? Command,
 43109        string? Pattern,
 43110        ImmutableArray<string> CommandPrefixes,
 43111        bool AllowSpaceAfterPrefix,
 43112        int PrefixMode,
 43113        bool IgnoreCase,
 43114        ImmutableArray<GeneratedTextFilter> TextFilters,
 43115        ImmutableArray<GeneratedFilter> Filters,
 43116        ImmutableArray<GeneratedChatMemberTransition> ChatMemberTransitions,
 43117        ImmutableArray<int> RoleRequirements,
 43118        ImmutableDictionary<string, GeneratedRouteValue> RouteValues);
 119
 120    private readonly record struct GeneratedRouteValue(
 121        string? Constraint,
 122        bool IsOptional);
 123
 124    private readonly record struct GeneratedChatMemberTransition(int OldStatus, int NewStatus);
 125
 1126    private sealed record GeneratedAutoAnswerCallback(
 1127        bool Enabled,
 1128        string? Text,
 1129        bool ShowAlert);
 130
 2131    private sealed record GeneratedTextFilter(string Value, int Mode, bool IgnoreCase);
 132
 8133    private sealed record GeneratedCallbackDataField(
 8134        string PropertyName,
 8135        string TypeName,
 8136        GeneratedCallbackDataFieldKind Kind);
 137
 26138    private sealed record GeneratedFilter(
 26139        string Kind,
 26140        ImmutableArray<string> StringValues,
 26141        ImmutableArray<long> LongValues,
 26142        string? CustomTypeName = null,
 26143        string? CustomAttributeExpression = null,
 26144        string? CustomContextTypeName = null,
 26145        ImmutableArray<string> CustomContextMetadataNames = default);
 146
 55147    private sealed record GeneratedParameter(string TypeName, GeneratedParameterKind Kind, string Name);
 148
 11149    private sealed record GeneratedErrorParameter(string TypeName, GeneratedErrorParameterKind Kind, string Name);
 150
 4151    private sealed record GeneratedStateGroup(
 4152        string TypeName,
 4153        string TypeMetadataName,
 4154        string? Namespace,
 4155        string Accessibility,
 4156        bool IsStatic,
 4157        string Prefix,
 4158        ImmutableArray<GeneratedStateProperty> States,
 4159        int SourceSpanStart);
 160
 6161    private sealed record GeneratedStateProperty(string Name, string Segment);
 162}

/_/src/TeleFlow.Generators/TelegramHandlerSourceGenerator.StateGroups.cs

#LineLine coverage
 1using System.Collections.Immutable;
 2using Microsoft.CodeAnalysis;
 3using Microsoft.CodeAnalysis.CSharp.Syntax;
 4
 5namespace TeleFlow.Generators;
 6
 7public sealed partial class TelegramHandlerSourceGenerator
 8{
 9    private static GeneratedStateGroup? GetStateGroup(GeneratorSyntaxContext context)
 10    {
 1911        if (context.SemanticModel.GetDeclaredSymbol(context.Node) is not INamedTypeSymbol type ||
 1912            !TryGetStateContainerPrefix(type, out string prefix))
 13        {
 1514            return null;
 15        }
 16
 417        ImmutableArray<GeneratedStateProperty> states = type
 418            .GetMembers()
 419            .OfType<IPropertySymbol>()
 820            .Where(static property => IsUsableStateProperty(property) && IsPartialStateProperty(property))
 621            .Select(static property => new GeneratedStateProperty(
 622                property.Name,
 623                GetStateSegment(property)))
 424            .OrderBy(static property => property.Name, StringComparer.Ordinal)
 425            .ToImmutableArray();
 26
 427        if (states.Length == 0)
 28        {
 029            return null;
 30        }
 31
 432        TypeDeclarationSyntax syntax = (TypeDeclarationSyntax)context.Node;
 33
 434        return new GeneratedStateGroup(
 435            TypeName: type.Name,
 436            TypeMetadataName: type.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat),
 437            Namespace: type.ContainingNamespace?.IsGlobalNamespace == false
 438                ? type.ContainingNamespace.ToDisplayString()
 439                : null,
 440            Accessibility: GetAccessibility(type.DeclaredAccessibility),
 441            IsStatic: type.IsStatic,
 442            Prefix: prefix,
 443            States: states,
 444            SourceSpanStart: syntax.SpanStart);
 45    }
 46
 47    private static string GetAccessibility(Accessibility accessibility)
 48    {
 449        return accessibility switch
 450        {
 451            Accessibility.Public => "public",
 052            Accessibility.Internal => "internal",
 053            _ => "internal"
 454        };
 55    }
 56}

Methods/Properties

.cctor()
Initialize(Microsoft.CodeAnalysis.IncrementalGeneratorInitializationContext)
GetHandlerMethod(Microsoft.CodeAnalysis.GeneratorSyntaxContext)
GetCallbackDataPayload(Microsoft.CodeAnalysis.GeneratorSyntaxContext)
GetClassBasedHandlerMethod(Microsoft.CodeAnalysis.GeneratorSyntaxContext)
GetErrorHandlerMethod(Microsoft.CodeAnalysis.GeneratorSyntaxContext)
TryBuildHandler(Microsoft.CodeAnalysis.IMethodSymbol,System.Boolean,TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedHandlerMethod&)
TryBuildErrorHandler(Microsoft.CodeAnalysis.IMethodSymbol,TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedErrorHandlerMethod&)
TryBuildErrorExceptionTypes(System.Collections.Generic.IReadOnlyList`1<Microsoft.CodeAnalysis.AttributeData>,System.Collections.Immutable.ImmutableArray`1<Microsoft.CodeAnalysis.ITypeSymbol>&,System.Collections.Immutable.ImmutableArray`1<System.String>&)
TryBuildErrorParameters(Microsoft.CodeAnalysis.IMethodSymbol,System.Collections.Immutable.ImmutableArray`1<Microsoft.CodeAnalysis.ITypeSymbol>,System.Collections.Immutable.ImmutableArray`1<TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedErrorParameter>&,System.String&)
TryBuildAutoAnswerCallback(Microsoft.CodeAnalysis.IMethodSymbol,System.Boolean,TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedAutoAnswerCallback&)
BuildRoutes(Microsoft.CodeAnalysis.IMethodSymbol,System.Boolean,Microsoft.CodeAnalysis.ITypeSymbol&)
TryBuildFilters(Microsoft.CodeAnalysis.IMethodSymbol,System.Collections.Immutable.ImmutableArray`1<TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedFilter>&)
AppendFilters(System.Collections.Immutable.ImmutableArray`1/Builder<TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedFilter>,Microsoft.CodeAnalysis.ISymbol)
AppendChatTypeFilters(System.Collections.Immutable.ImmutableArray`1/Builder<TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedFilter>,Microsoft.CodeAnalysis.ISymbol,System.String,System.String)
AppendSenderIdFilters(System.Collections.Immutable.ImmutableArray`1/Builder<TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedFilter>,Microsoft.CodeAnalysis.ISymbol,System.String,System.String)
AppendMarkerFilter(System.Collections.Immutable.ImmutableArray`1/Builder<TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedFilter>,Microsoft.CodeAnalysis.ISymbol,TeleFlow.Generators.TelegramBuiltInFilterSpec)
SupportsGeneratedFilter(TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedFilter,TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedHandlerKind)
TryPrepareCustomFilters(System.Collections.Immutable.ImmutableArray`1<TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedFilter>,TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedHandlerKind,System.Collections.Immutable.ImmutableArray`1<TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedFilter>&)
TryResolveCustomFilterContextType(TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedFilter,TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedHandlerKind,System.String&)
ToFullyQualifiedTypeName(System.String)
CanonicalizeChatUsername(System.String)
HasChatMemberTransitionAttributes(Microsoft.CodeAnalysis.IMethodSymbol)
TryBuildRoleRequirements(Microsoft.CodeAnalysis.IMethodSymbol,System.Collections.Immutable.ImmutableArray`1<System.Int32>&)
TryBuildChatMemberTransitions(Microsoft.CodeAnalysis.IMethodSymbol,System.Collections.Immutable.ImmutableArray`1<TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedChatMemberTransition>&)
ToMetadataRouteKind(TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedHandlerKind)
IsInvalidCustomFilterType(Microsoft.CodeAnalysis.ITypeSymbol)
TryGetTelegramFilterContextTypes(Microsoft.CodeAnalysis.ITypeSymbol,Microsoft.CodeAnalysis.ITypeSymbol,System.Collections.Immutable.ImmutableArray`1<System.String>&)
GetModuleName(Microsoft.CodeAnalysis.INamedTypeSymbol)
TryGetExpectedContextType(TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedHandlerKind,System.String&)
HasExactlyOneExpectedContext(Microsoft.CodeAnalysis.IMethodSymbol,System.String)
HasValidCallbackPayloadParameter(Microsoft.CodeAnalysis.IMethodSymbol,Microsoft.CodeAnalysis.ITypeSymbol)
IsInvalidCallbackPayloadType(Microsoft.CodeAnalysis.ITypeSymbol)
IsAccessibleFromGeneratedCode(Microsoft.CodeAnalysis.INamedTypeSymbol)
TryGetCallbackDataFields(Microsoft.CodeAnalysis.INamedTypeSymbol,System.Boolean&,System.Collections.Immutable.ImmutableArray`1<TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedCallbackDataField>&)
TryGetConstructorCallbackDataFields(Microsoft.CodeAnalysis.INamedTypeSymbol,Microsoft.CodeAnalysis.IMethodSymbol,System.Boolean&,System.Collections.Immutable.ImmutableArray`1<TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedCallbackDataField>&)
TryGetPropertyCallbackDataFields(Microsoft.CodeAnalysis.INamedTypeSymbol,System.Collections.Immutable.ImmutableArray`1<TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedCallbackDataField>&)
TryGetCallbackDataFieldKind(Microsoft.CodeAnalysis.ITypeSymbol,TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedCallbackDataFieldKind&)
IsInvalidConcreteNamedType(Microsoft.CodeAnalysis.ITypeSymbol)
IsSupportedReturnType(Microsoft.CodeAnalysis.ITypeSymbol)
TryGetErrorReturnKind(Microsoft.CodeAnalysis.ITypeSymbol,TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedErrorReturnKind&)
IsValidCommand(System.String)
GetConstructorString(Microsoft.CodeAnalysis.AttributeData)
TryGetPrefixes(Microsoft.CodeAnalysis.AttributeData,System.Collections.Immutable.ImmutableArray`1<System.String>&)
GetCommandPrefixMode(Microsoft.CodeAnalysis.AttributeData)
HasInvalidNoPrefixConfiguration(Microsoft.CodeAnalysis.AttributeData,System.Int32)
IsSupportedCommandPrefixMode(System.Int32)
HasNamedArgument(Microsoft.CodeAnalysis.AttributeData,System.String)
CommandPatternStartsWithPrefix(System.String,System.Collections.Immutable.ImmutableArray`1<System.String>)
GetNamedBool(Microsoft.CodeAnalysis.AttributeData,System.String,System.Boolean)
TryParseTemplateRouteValues(System.String,System.Collections.Immutable.ImmutableDictionary`2<System.String,TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedRouteValue>&)
TryParseRegexRouteValues(System.String,System.Collections.Immutable.ImmutableDictionary`2<System.String,TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedRouteValue>&)
ContainsBrace(System.String)
TryResolveRouteValueNames(Microsoft.CodeAnalysis.IMethodSymbol,System.Collections.Immutable.ImmutableArray`1<TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedRoute>,System.Collections.Immutable.ImmutableHashSet`1<System.String>&)
IsSupportedRouteValueParameterType(Microsoft.CodeAnalysis.ITypeSymbol)
IsNullableRouteValueParameter(Microsoft.CodeAnalysis.ITypeSymbol)
GetRouteValueParameterSpecialType(Microsoft.CodeAnalysis.ITypeSymbol)
RouteConstraintMatchesParameter(System.String,Microsoft.CodeAnalysis.ITypeSymbol)
GetRouteValueTypeName(TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedRouteValue)
BuildTextFilters(Microsoft.CodeAnalysis.IMethodSymbol,System.Boolean)
GetRouteAttributes(Microsoft.CodeAnalysis.IMethodSymbol,System.String,System.Boolean)
GetGenericRouteAttributes(Microsoft.CodeAnalysis.IMethodSymbol,System.String,System.Boolean)
IsClassBasedHandlerType(Microsoft.CodeAnalysis.INamedTypeSymbol)
IsClassBasedRouteCompatible(Microsoft.CodeAnalysis.INamedTypeSymbol,TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedHandlerKind,Microsoft.CodeAnalysis.ITypeSymbol)
GetDeclaredHandleAsyncMethods(Microsoft.CodeAnalysis.INamedTypeSymbol)
TryGetCallbackHandlerPayloadType(Microsoft.CodeAnalysis.INamedTypeSymbol,Microsoft.CodeAnalysis.ITypeSymbol&)
IsAssignableTo(Microsoft.CodeAnalysis.INamedTypeSymbol,System.String)
IsDirectlyAssignableTo(Microsoft.CodeAnalysis.INamedTypeSymbol,System.String)
TryBuildStates(Microsoft.CodeAnalysis.IMethodSymbol,System.Collections.Immutable.ImmutableArray`1<System.String>&,System.String&)
GetGenericStateAttributes(Microsoft.CodeAnalysis.ISymbol)
TryResolveTypedState(Microsoft.CodeAnalysis.AttributeData,System.String&)
TryResolveSceneStep(Microsoft.CodeAnalysis.IMethodSymbol,Microsoft.CodeAnalysis.AttributeData,System.String&,System.String&)
TryGetStateContainerPrefix(Microsoft.CodeAnalysis.INamedTypeSymbol,System.String&)
TryGetStateGroupPrefix(Microsoft.CodeAnalysis.INamedTypeSymbol,System.String&)
TryGetScenePrefix(Microsoft.CodeAnalysis.INamedTypeSymbol,System.String&)
TryGetAttributeStringPrefix(Microsoft.CodeAnalysis.INamedTypeSymbol,System.String,System.String&)
GetStateSegment(Microsoft.CodeAnalysis.IPropertySymbol)
IsUsableStateProperty(Microsoft.CodeAnalysis.IPropertySymbol)
IsPartialStateProperty(Microsoft.CodeAnalysis.IPropertySymbol)
ToCamelCase(System.String)
BuildParameters(Microsoft.CodeAnalysis.IMethodSymbol,TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedHandlerKind,System.String,Microsoft.CodeAnalysis.ITypeSymbol,System.Collections.Immutable.ImmutableHashSet`1<System.String>)
GetParameterKind(Microsoft.CodeAnalysis.IParameterSymbol,TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedHandlerKind,System.String,Microsoft.CodeAnalysis.ITypeSymbol,System.Collections.Immutable.ImmutableHashSet`1<System.String>)
BuildErrorParameters(Microsoft.CodeAnalysis.IMethodSymbol)
GetErrorParameterKind(Microsoft.CodeAnalysis.IParameterSymbol)
IsExceptionType(Microsoft.CodeAnalysis.ITypeSymbol)
IsTelegramContextType(Microsoft.CodeAnalysis.ITypeSymbol)
IsAssignableFrom(Microsoft.CodeAnalysis.ITypeSymbol,Microsoft.CodeAnalysis.ITypeSymbol)
IsErrorRouteValueParameterType(Microsoft.CodeAnalysis.ITypeSymbol)
IsRouteValueParameterType(Microsoft.CodeAnalysis.ITypeSymbol)
GenerateSource(System.Collections.Generic.IReadOnlyList`1<TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedHandlerMethod>,System.Collections.Generic.IReadOnlyList`1<TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedErrorHandlerMethod>,System.Collections.Generic.IReadOnlyList`1<TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedCallbackDataPayload>)
AppendCallbackDataCodecRegistration(System.Text.StringBuilder,System.Collections.Generic.IReadOnlyList`1<TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedCallbackDataPayload>)
GenerateStateGroupsSource(System.Collections.Generic.IReadOnlyList`1<TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedStateGroup>)
AppendRegistration(System.Text.StringBuilder,TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedHandlerMethod,TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedRoute,System.Int32,System.Int32)
AppendAutoAnswerCallback(System.Text.StringBuilder,TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedAutoAnswerCallback)
AppendFilter(System.Text.StringBuilder,TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedFilter)
AppendErrorRegistration(System.Text.StringBuilder,TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedErrorHandlerMethod,System.String,System.Int32,System.Int32)
AppendCallbackDataCodec(System.Text.StringBuilder,TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedCallbackDataPayload,System.Int32)
GetCallbackDataPackExpression(TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedCallbackDataField)
GetCallbackDataUnpackExpression(TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedCallbackDataPayload,TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedCallbackDataField,System.Int32)
AppendCallbackDataHelpers(System.Text.StringBuilder)
AppendInvoker(System.Text.StringBuilder,TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedHandlerMethod,System.Int32)
AppendErrorInvoker(System.Text.StringBuilder,TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedErrorHandlerMethod,System.Int32)
AppendErrorInvocationArguments(System.Text.StringBuilder,TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedErrorHandlerMethod)
ToNullableLiteral(System.String)
ToTypeOfNullable(System.String)
ToLiteral(System.String)
ToBoolLiteral(System.Boolean)
ToCommandPrefixModeLiteral(System.Int32)
ToAttributeCreationExpression(Microsoft.CodeAnalysis.AttributeData)
ToAttributeArgumentExpression(Microsoft.CodeAnalysis.TypedConstant)
ToPrimitiveLiteral(System.Object,Microsoft.CodeAnalysis.ITypeSymbol)
ToCharLiteral(System.Char)
ToEnumLiteral(Microsoft.CodeAnalysis.TypedConstant)
ToArrayLiteral(Microsoft.CodeAnalysis.TypedConstant)
.ctor(System.String,System.String,System.String,System.String,System.String,System.String,System.String,TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedAutoAnswerCallback,System.Collections.Immutable.ImmutableArray`1<TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedRoute>,System.Collections.Immutable.ImmutableArray`1<System.String>,System.Collections.Immutable.ImmutableArray`1<TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedParameter>,System.String,System.Int32)
.ctor(System.String,System.String,System.String,System.String,System.String,System.Collections.Immutable.ImmutableArray`1<System.String>,System.String,TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedErrorReturnKind,System.Collections.Immutable.ImmutableArray`1<TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedErrorParameter>,System.String,System.Int32)
.ctor(System.String,System.String,System.String,System.Boolean,System.Collections.Immutable.ImmutableArray`1<TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedCallbackDataField>,System.String,System.Int32)
.ctor(TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedHandlerKind,TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedRouteKind,System.String,System.String,System.Collections.Immutable.ImmutableArray`1<System.String>,System.Boolean,System.Int32,System.Boolean,System.Collections.Immutable.ImmutableArray`1<TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedTextFilter>,System.Collections.Immutable.ImmutableArray`1<TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedFilter>,System.Collections.Immutable.ImmutableArray`1<TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedChatMemberTransition>,System.Collections.Immutable.ImmutableArray`1<System.Int32>,System.Collections.Immutable.ImmutableDictionary`2<System.String,TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedRouteValue>)
.ctor(System.Boolean,System.String,System.Boolean)
.ctor(System.String,System.Int32,System.Boolean)
.ctor(System.String,System.String,TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedCallbackDataFieldKind)
.ctor(System.String,System.Collections.Immutable.ImmutableArray`1<System.String>,System.Collections.Immutable.ImmutableArray`1<System.Int64>,System.String,System.String,System.String,System.Collections.Immutable.ImmutableArray`1<System.String>)
.ctor(System.String,TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedParameterKind,System.String)
.ctor(System.String,TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedErrorParameterKind,System.String)
.ctor(System.String,System.String,System.String,System.String,System.Boolean,System.String,System.Collections.Immutable.ImmutableArray`1<TeleFlow.Generators.TelegramHandlerSourceGenerator/GeneratedStateProperty>,System.Int32)
.ctor(System.String,System.String)
GetStateGroup(Microsoft.CodeAnalysis.GeneratorSyntaxContext)
GetAccessibility(Microsoft.CodeAnalysis.Accessibility)