< Summary

Line coverage
89%
Covered lines: 1418
Uncovered lines: 172
Coverable lines: 1590
Total lines: 2908
Line coverage: 89.1%
Branch coverage
80%
Covered branches: 688
Total branches: 853
Branch coverage: 80.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
File 1: AnalyzeCallbackDataType(...)83.33%1212100%
File 1: GetCallbackDataFields()62.5%10868.75%
File 2: AnalyzeClassLevelRouteUsage(...)100%44100%
File 2: AnalyzeClassBasedHandlerType(...)79.16%252488.63%
File 2: GetDeclaredRouteMethodsExceptHandleAsync(...)100%1010100%
File 2: GetDeclaredHandleAsyncMethods(...)50%66100%
File 2: IsClassBasedHandlerType(...)100%44100%
File 2: IsClassBasedRouteCompatible(...)86.36%232285.71%
File 2: TryGetCallbackHandlerPayloadType(...)100%66100%
File 2: IsAssignableTo(...)100%66100%
File 3: Initialize(...)90%101097.87%
File 3: AnalyzeNamedType(...)100%11100%
File 3: AnalyzeMethod(...)100%22100%
File 3: AnalyzeHandlerMethod(...)78.57%322882.81%
File 3: AnalyzeAutoAnswerCallback(...)75%4492.3%
File 4: .cctor()100%11100%
File 4: .ctor()100%11100%
File 4: CreateDescriptor(...)100%11100%
File 5: AnalyzeErrorHandlerMethod(...)85.71%1414100%
File 5: GetErrorHandlerExceptionTypes(...)70%151064.28%
File 5: ValidateErrorHandlerParameters(...)94.44%181891.11%
File 5: IsSupportedErrorHandlerReturnType(...)50%1414100%
File 5: IsExceptionType(...)50%22100%
File 5: IsTelegramContextType(...)50%22100%
File 5: IsAssignableFrom(...)66.66%7666.66%
File 5: ReportInvalidErrorHandler(...)100%11100%
File 6: AnalyzeInheritedHandlerMethods(...)100%44100%
File 6: GetInheritedHandlerMethods()100%1818100%
File 6: IsConcreteHandlerCandidate(...)100%2222100%
File 6: IsOverriddenBy(...)83.33%66100%
File 7: AnalyzeTelegramModuleType(...)90%1010100%
File 7: HasTelegramHandlerMethods(...)66.66%66100%
File 8: .cctor()100%11100%
File 8: AnalyzeMissingRouteAttribute(...)80%101092.3%
File 8: TryGetRouteLessHandlerAttribute(...)100%88100%
File 8: TryGetFirstKnownAttribute(...)100%44100%
File 8: TryGetFirstKnownGenericAttribute(...)100%44100%
File 8: TryGetFirst(...)100%22100%
File 9: GetRouteKind(...)96.42%2828100%
File 9: GetMessageRouteAttributeCount(...)100%11100%
File 9: HasAnyMessageRouteAttribute(...)100%11100%
File 9: GetChatMemberRouteAttributeCount(...)100%11100%
File 9: AnalyzeCommandRoutes(...)77.77%231875.67%
File 9: AnalyzeTemplateAndRegexRoutes(...)76.66%503071.76%
File 9: ValidateRouteValueParameters(...)66.66%813061.7%
File 9: AnalyzeBuiltInFilters(...)93.18%4444100%
File 9: AnalyzeChatTypeFilters(...)90%1010100%
File 9: AnalyzeSenderIdFilters(...)90%1010100%
File 9: AnalyzeCustomFilters(...)76.92%602662.96%
File 9: ToMetadataRouteKind(...)60%5575%
File 9: CanonicalizeChatUsername(...)75%4475%
File 9: GetCustomFilterAttributes(...)100%11100%
File 9: IsInvalidCustomFilterType(...)66.66%151271.42%
File 9: TryGetTelegramFilterContextTypes(...)100%11100%
File 9: TryGetParameterizedTelegramFilterContextTypes(...)50%4491.66%
File 9: CustomFilterSupportsRouteKind(...)100%66100%
File 9: AnalyzeChatMemberTransitions(...)69.23%2626100%
File 9: AnalyzeTelegramRoleRequirements(...)100%66100%
File 9: TryParseRouteTemplate(...)87.5%252488.88%
File 9: TryParseRouteRegex(...)50%2289.47%
File 9: GetConstructorString(...)50%44100%
File 9: TryGetPrefixes(...)83.33%121288.23%
File 9: GetCommandPrefixMode(...)100%66100%
File 9: TryValidateCommandPrefixMode(...)83.33%7675%
File 9: IsSupportedCommandPrefixMode(...)50%22100%
File 9: HasNamedArgument(...)100%11100%
File 9: CommandPatternStartsWithPrefix(...)100%11100%
File 9: GetNamedBool(...)50%14640%
File 9: ContainsBrace(...)100%22100%
File 9: IsSupportedRouteValueParameterType(...)80%2020100%
File 9: IsNullableRouteValueParameter(...)66.66%66100%
File 9: GetRouteValueParameterSpecialType(...)0%7280%
File 9: RouteConstraintMatchesParameter(...)0%4260%
File 9: ValidateCallbackRoute(...)91.66%1212100%
File 9: HasCallbackDataAttribute(...)50%22100%
File 9: IsInvalidCallbackPayloadType(...)66.66%121285.71%
File 9: ValidateContextParameter(...)94.44%1818100%
File 9: GetDiagnosticHandlerKind(...)75%4483.33%
File 9: ValidateCancellationTokens(...)100%22100%
File 9: IsSupportedReturnType(...)100%22100%
File 9: IsValidCommand(...)100%88100%
File 9: HasAnyRouteAttribute(...)100%44100%
File 9: GetRouteAttributes(...)100%44100%
File 9: GetGenericRouteAttributes(...)100%44100%
File 10: AnalyzeSceneType(...)79.41%343492.72%
File 10: AnalyzeSceneStep(...)75%1616100%
File 10: HasStateBindingAttributes(...)100%22100%
File 10: ReportDuplicateSceneStateIds(...)75%44100%
File 10: GetSceneStateSegment(...)100%88100%
File 10: ToSceneCamelCase(...)50%22100%
File 11: AnalyzeStateGroupType(...)63.63%572258.53%
File 11: ValidateTypedStateAttributes(...)72.22%211879.16%
File 11: HasStateContainerAttribute(...)50%22100%
File 11: IsUsableStateProperty(...)100%44100%

File(s)

/_/src/TeleFlow.Generators/TelegramHandlerAnalyzer.CallbackData.cs

#LineLine coverage
 1using System.Collections.Concurrent;
 2using Microsoft.CodeAnalysis;
 3using Microsoft.CodeAnalysis.Diagnostics;
 4
 5namespace TeleFlow.Generators;
 6
 7public sealed partial class TelegramHandlerAnalyzer
 8{
 9    private static void AnalyzeCallbackDataType(
 10        SymbolAnalysisContext context,
 11        INamedTypeSymbol type,
 12        ConcurrentBag<CallbackPrefixRegistration> callbackPrefixes)
 13    {
 9914        AttributeData? attribute = TelegramHandlerSymbols.GetFirstAttribute(
 9915            type,
 9916            TelegramHandlerSymbols.CallbackDataAttribute);
 17
 9918        if (attribute is null)
 19        {
 9420            return;
 21        }
 22
 1023        Location? location = type.Locations.FirstOrDefault(static candidate => candidate.IsInSource);
 524        string? prefix = attribute.ConstructorArguments.Length > 0
 525            ? attribute.ConstructorArguments[0].Value as string
 526            : null;
 27
 528        if (!TelegramCallbackDataFacts.IsValidPayloadPrefix(prefix))
 29        {
 130            context.ReportDiagnostic(Diagnostic.Create(
 131                InvalidCallbackData,
 132                location,
 133                $"Callback data payload type '{type.Name}' must declare a non-empty prefix without ':', '%', or whitespa
 134            return;
 35        }
 36
 437        callbackPrefixes.Add(new CallbackPrefixRegistration(prefix!, location));
 38
 2639        foreach (IPropertySymbol field in GetCallbackDataFields(type))
 40        {
 941            if (!TelegramCallbackDataFacts.IsSupportedFieldType(field.Type))
 42            {
 143                context.ReportDiagnostic(Diagnostic.Create(
 144                    InvalidCallbackData,
 145                    field.Locations.FirstOrDefault(static candidate => candidate.IsInSource) ?? location,
 146                    $"Callback data field '{type.Name}.{field.Name}' must be string, int, long, bool, or enum."));
 47            }
 48        }
 449    }
 50
 51    private static IEnumerable<IPropertySymbol> GetCallbackDataFields(INamedTypeSymbol type)
 52    {
 453        IMethodSymbol? constructor = type
 454            .Constructors
 55            .Where(static candidate => !candidate.IsStatic && candidate.DeclaredAccessibility == Accessibility.Public)
 56            .Where(static candidate => candidate.Parameters.Length > 0)
 57            .OrderByDescending(static candidate => candidate.Parameters.Length)
 458            .FirstOrDefault();
 59
 460        if (constructor is not null)
 61        {
 2662            foreach (IParameterSymbol parameter in constructor.Parameters)
 63            {
 964                IPropertySymbol? property = type
 965                    .GetMembers()
 966                    .OfType<IPropertySymbol>()
 3867                    .FirstOrDefault(property => string.Equals(
 3868                        property.Name,
 3869                        parameter.Name,
 3870                        StringComparison.OrdinalIgnoreCase));
 71
 972                if (property is not null)
 73                {
 974                    yield return property;
 75                }
 76            }
 77
 478            yield break;
 79        }
 80
 081        foreach (IPropertySymbol property in type
 082                     .GetMembers()
 083                     .OfType<IPropertySymbol>()
 84                     .Where(static property => property.DeclaredAccessibility == Accessibility.Public && !property.IsSta
 85        {
 086            yield return property;
 87        }
 088    }
 89
 90}

/_/src/TeleFlow.Generators/TelegramHandlerAnalyzer.ClassBasedHandlers.cs

#LineLine coverage
 1using System.Collections.Concurrent;
 2using System.Collections.Immutable;
 3using Microsoft.CodeAnalysis;
 4using Microsoft.CodeAnalysis.Diagnostics;
 5
 6namespace TeleFlow.Generators;
 7
 8public sealed partial class TelegramHandlerAnalyzer
 9{
 10    private static void AnalyzeClassLevelRouteUsage(
 11        SymbolAnalysisContext context,
 12        INamedTypeSymbol type)
 13    {
 9914        if (IsClassBasedHandlerType(type) ||
 9915            !TelegramHandlerSymbols.HasAnyRouteAttribute(type))
 16        {
 9817            return;
 18        }
 19
 220        Location? location = type.Locations.FirstOrDefault(static candidate => candidate.IsInSource);
 121        string typeName = type.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat);
 22
 123        context.ReportDiagnostic(Diagnostic.Create(
 124            InvalidClassBasedHandler,
 125            location,
 126            $"Telegram handler type '{typeName}' declares class-level route metadata but does not derive from a TeleFlow
 127    }
 28
 29    private static void AnalyzeClassBasedHandlerType(
 30        SymbolAnalysisContext context,
 31        INamedTypeSymbol type,
 32        ConcurrentBag<CommandRegistration> commands)
 33    {
 9934        if (!IsClassBasedHandlerType(type))
 35        {
 8936            return;
 37        }
 38
 1039        ImmutableArray<IMethodSymbol> handleMethods = GetDeclaredHandleAsyncMethods(type);
 1040        bool hasTypeRouteAttributes = TelegramHandlerSymbols.HasAnyRouteAttribute(type);
 2041        bool hasHandleRouteAttributes = handleMethods.Any(static method => TelegramHandlerSymbols.HasAnyRouteAttribute(m
 1042        IMethodSymbol[] routeMethods = GetDeclaredRouteMethodsExceptHandleAsync(type).ToArray();
 43
 1044        if (!hasTypeRouteAttributes &&
 1045            !hasHandleRouteAttributes &&
 1046            routeMethods.Length == 0)
 47        {
 048            return;
 49        }
 50
 2051        Location? location = type.Locations.FirstOrDefault(static candidate => candidate.IsInSource);
 1052        string typeName = type.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat);
 53
 2254        foreach (IMethodSymbol routeMethod in routeMethods)
 55        {
 156            context.ReportDiagnostic(Diagnostic.Create(
 157                InvalidClassBasedHandler,
 158                routeMethod.Locations.FirstOrDefault(static candidate => candidate.IsInSource) ?? location,
 159                $"Class-based Telegram handler type '{typeName}' must declare route metadata on the type or HandleAsync 
 60        }
 61
 1062        if (type.TypeKind is TypeKind.Interface || type.IsAbstract)
 63        {
 064            context.ReportDiagnostic(Diagnostic.Create(
 065                InvalidHandlerType,
 066                location,
 067                typeName));
 68        }
 69
 1070        if (handleMethods.Length != 1)
 71        {
 272            context.ReportDiagnostic(Diagnostic.Create(
 273                InvalidClassBasedHandler,
 274                location,
 275                $"Class-based Telegram handler type '{typeName}' must declare exactly one public instance HandleAsync me
 276            return;
 77        }
 78
 879        IMethodSymbol handleMethod = handleMethods[0];
 880        HandlerKind? routeKind = GetRouteKind(
 881            handleMethod,
 882            includeClassRouteAttributes: true,
 883            out ITypeSymbol? callbackPayloadType,
 884            out _,
 885            out bool hasMixedCallbackRoutes);
 86
 887        if (!hasMixedCallbackRoutes &&
 888            routeKind is not null &&
 889            !IsClassBasedRouteCompatible(type, routeKind.Value, callbackPayloadType, out string reason))
 90        {
 391            context.ReportDiagnostic(Diagnostic.Create(
 392                InvalidClassBasedHandler,
 393                location,
 394                reason));
 95        }
 96
 897        AnalyzeHandlerMethod(context, handleMethod, commands, includeClassRouteAttributes: true);
 898    }
 99
 100    private static IEnumerable<IMethodSymbol> GetDeclaredRouteMethodsExceptHandleAsync(INamedTypeSymbol type)
 101    {
 10102        return type.GetMembers()
 10103            .OfType<IMethodSymbol>()
 10104            .Where(static method =>
 31105                method.DeclaredAccessibility == Accessibility.Public &&
 31106                !method.IsStatic &&
 31107                method.MethodKind == MethodKind.Ordinary &&
 31108                !method.IsGenericMethod &&
 31109                !string.Equals(method.Name, "HandleAsync", StringComparison.Ordinal) &&
 31110                TelegramHandlerSymbols.HasAnyRouteAttribute(method));
 111    }
 112
 113    private static ImmutableArray<IMethodSymbol> GetDeclaredHandleAsyncMethods(INamedTypeSymbol type)
 114    {
 11115        return type.GetMembers("HandleAsync")
 11116            .OfType<IMethodSymbol>()
 11117            .Where(static method =>
 11118                method.DeclaredAccessibility == Accessibility.Public &&
 11119                !method.IsStatic &&
 11120                method.MethodKind == MethodKind.Ordinary &&
 11121                !method.IsGenericMethod)
 11122            .ToImmutableArray();
 123    }
 124
 125    private static bool IsClassBasedHandlerType(INamedTypeSymbol type)
 126    {
 432127        return IsAssignableTo(type, TelegramHandlerSymbols.MessageHandler) ||
 432128               IsAssignableTo(type, TelegramHandlerSymbols.CallbackHandler) ||
 432129               IsAssignableTo(type, TelegramHandlerSymbols.ChatMemberUpdateHandler);
 130    }
 131
 132    private static bool IsClassBasedRouteCompatible(
 133        INamedTypeSymbol type,
 134        HandlerKind kind,
 135        ITypeSymbol? callbackPayloadType,
 136        out string reason)
 137    {
 7138        string typeName = type.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat);
 139
 7140        if (IsAssignableTo(type, TelegramHandlerSymbols.MessageHandler))
 141        {
 3142            if (kind == HandlerKind.Message)
 143            {
 2144                reason = string.Empty;
 2145                return true;
 146            }
 147
 1148            reason = $"MessageHandler '{typeName}' can declare only message, text, command, template, or regex routes.";
 1149            return false;
 150        }
 151
 4152        if (TryGetCallbackHandlerPayloadType(type, out ITypeSymbol handlerPayloadType))
 153        {
 2154            if (kind == HandlerKind.Callback &&
 2155                callbackPayloadType is not null &&
 2156                SymbolEqualityComparer.Default.Equals(handlerPayloadType, callbackPayloadType))
 157            {
 1158                reason = string.Empty;
 1159                return true;
 160            }
 161
 1162            reason = $"CallbackHandler<TPayload> '{typeName}' must use matching CallbackAttribute<TPayload> route metada
 1163            return false;
 164        }
 165
 2166        if (IsAssignableTo(type, TelegramHandlerSymbols.CallbackHandler))
 167        {
 1168            if (kind == HandlerKind.Callback &&
 1169                callbackPayloadType is null)
 170            {
 0171                reason = string.Empty;
 0172                return true;
 173            }
 174
 1175            reason = $"CallbackHandler '{typeName}' can declare only raw CallbackAttribute route metadata.";
 1176            return false;
 177        }
 178
 1179        if (IsAssignableTo(type, TelegramHandlerSymbols.ChatMemberUpdateHandler) &&
 1180            kind == HandlerKind.ChatMember)
 181        {
 1182            reason = string.Empty;
 1183            return true;
 184        }
 185
 0186        reason = $"ChatMemberUpdateHandler '{typeName}' can declare only chat member update routes.";
 0187        return false;
 188    }
 189
 190    private static bool TryGetCallbackHandlerPayloadType(
 191        INamedTypeSymbol type,
 192        out ITypeSymbol payloadType)
 193    {
 24194        for (INamedTypeSymbol? current = type; current is not null; current = current.BaseType)
 195        {
 10196            if (current.OriginalDefinition.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat) ==
 10197                TelegramHandlerSymbols.GenericCallbackHandler &&
 10198                current.TypeArguments.Length == 1)
 199            {
 2200                payloadType = current.TypeArguments[0];
 2201                return true;
 202            }
 203        }
 204
 2205        payloadType = null!;
 2206        return false;
 207    }
 208
 209    private static bool IsAssignableTo(INamedTypeSymbol type, string metadataName)
 210    {
 7942211        for (INamedTypeSymbol? current = type; current is not null; current = current.BaseType)
 212        {
 2757213            if (current.OriginalDefinition.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat) == metadataName
 2757214                current.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat) == metadataName)
 215            {
 59216                return true;
 217            }
 218        }
 219
 1214220        return false;
 221    }
 222}

/_/src/TeleFlow.Generators/TelegramHandlerAnalyzer.cs

#LineLine coverage
 1using System.Collections.Concurrent;
 2using Microsoft.CodeAnalysis;
 3using Microsoft.CodeAnalysis.Diagnostics;
 4
 5namespace TeleFlow.Generators;
 6
 7[DiagnosticAnalyzer(LanguageNames.CSharp)]
 8public sealed partial class TelegramHandlerAnalyzer : DiagnosticAnalyzer
 9{
 10    public override void Initialize(AnalysisContext context)
 11    {
 4612        if (context is null)
 13        {
 014            throw new ArgumentNullException(nameof(context));
 15        }
 16
 4617        context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
 4618        context.EnableConcurrentExecution();
 19
 4620        context.RegisterCompilationStartAction(static compilationContext =>
 4621        {
 4622            ConcurrentBag<CommandRegistration> commands = new ConcurrentBag<CommandRegistration>();
 4623            ConcurrentBag<CallbackPrefixRegistration> callbackPrefixes = new ConcurrentBag<CallbackPrefixRegistration>()
 4624
 4625            compilationContext.RegisterSymbolAction(
 14926                symbolContext => AnalyzeMethod(symbolContext, commands),
 4627                SymbolKind.Method);
 4628
 4629            compilationContext.RegisterSymbolAction(
 9930                symbolContext => AnalyzeNamedType(symbolContext, commands, callbackPrefixes),
 4631                SymbolKind.NamedType);
 4632
 4633            compilationContext.RegisterCompilationEndAction(context =>
 4634            {
 9435                foreach (IGrouping<string, CommandRegistration> duplicateGroup in commands
 436                             .GroupBy(static command => command.Key, StringComparer.Ordinal)
 4937                             .Where(static group => group.Count() > 1))
 4638                {
 639                    foreach (CommandRegistration registration in duplicateGroup)
 4640                    {
 241                        context.ReportDiagnostic(Diagnostic.Create(
 242                            DuplicateCommand,
 243                            registration.Location,
 244                            registration.Display));
 4645                    }
 4646                }
 4647
 9448                foreach (IGrouping<string, CallbackPrefixRegistration> duplicateGroup in callbackPrefixes
 449                             .GroupBy(static registration => registration.Prefix, StringComparer.Ordinal)
 4950                             .Where(static group => group.Count() > 1))
 4651                {
 652                    foreach (CallbackPrefixRegistration registration in duplicateGroup)
 4653                    {
 254                        context.ReportDiagnostic(Diagnostic.Create(
 255                            DuplicateCallbackDataPrefix,
 256                            registration.Location,
 257                            duplicateGroup.Key));
 4658                    }
 4659                }
 9260            });
 9261        });
 4662    }
 63
 64    private static void AnalyzeNamedType(
 65        SymbolAnalysisContext context,
 66        ConcurrentBag<CommandRegistration> commands,
 67        ConcurrentBag<CallbackPrefixRegistration> callbackPrefixes)
 68    {
 9969        INamedTypeSymbol type = (INamedTypeSymbol)context.Symbol;
 70
 9971        AnalyzeCallbackDataType(context, type, callbackPrefixes);
 9972        AnalyzeStateGroupType(context, type);
 9973        AnalyzeSceneType(context, type);
 9974        AnalyzeTelegramModuleType(context, type);
 9975        AnalyzeClassLevelRouteUsage(context, type);
 9976        AnalyzeClassBasedHandlerType(context, type, commands);
 9977        AnalyzeInheritedHandlerMethods(context, type);
 9978    }
 79
 80    private static void AnalyzeMethod(
 81        SymbolAnalysisContext context,
 82        ConcurrentBag<CommandRegistration> commands)
 83    {
 14984        IMethodSymbol method = (IMethodSymbol)context.Symbol;
 85
 14986        AnalyzeErrorHandlerMethod(context, method);
 87
 14988        if (IsClassBasedHandlerType(method.ContainingType))
 89        {
 1190            return;
 91        }
 92
 13893        AnalyzeHandlerMethod(context, method, commands, includeClassRouteAttributes: false);
 13894    }
 95
 96    private static void AnalyzeHandlerMethod(
 97        SymbolAnalysisContext context,
 98        IMethodSymbol method,
 99        ConcurrentBag<CommandRegistration> commands,
 100        bool includeClassRouteAttributes)
 101    {
 146102        bool hasRouteAttribute = HasAnyRouteAttribute(method, includeClassRouteAttributes);
 146103        bool hasSceneStep = TelegramHandlerSymbols.HasAttribute(
 146104            method,
 146105            TelegramHandlerSymbols.SceneStepAttribute,
 146106            inherit: true);
 107
 146108        if (!hasRouteAttribute)
 109        {
 57110            if (hasSceneStep)
 111            {
 2112                Location? sceneStepLocation = method.Locations.FirstOrDefault(static candidate => candidate.IsInSource);
 113
 1114                context.ReportDiagnostic(Diagnostic.Create(
 1115                    InvalidSceneStep,
 1116                    sceneStepLocation,
 1117                    "SceneStepAttribute requires an explicit Telegram route attribute."));
 118            }
 119
 57120            AnalyzeMissingRouteAttribute(context, method);
 57121            AnalyzeAutoAnswerCallback(context, method, routeKind: null);
 57122            return;
 123        }
 124
 89125        HandlerKind? routeKind = GetRouteKind(method, includeClassRouteAttributes, out ITypeSymbol? callbackPayloadType,
 89126        string displayName = method.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat);
 178127        Location? location = method.Locations.FirstOrDefault(static candidate => candidate.IsInSource);
 128
 89129        if (method.ContainingType.TypeKind is TypeKind.Interface)
 130        {
 0131            context.ReportDiagnostic(Diagnostic.Create(
 0132                InvalidHandlerType,
 0133                location,
 0134                method.ContainingType.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)));
 135        }
 136
 89137        if (method.DeclaredAccessibility != Accessibility.Public || method.IsStatic)
 138        {
 0139            context.ReportDiagnostic(Diagnostic.Create(
 0140                InvalidHandlerMethod,
 0141                location,
 0142                displayName));
 143        }
 144
 89145        if (hasMixedCallbackRoutes || routeKind is null)
 146        {
 3147            if ((hasRawCallback || callbackPayloadType is not null) &&
 3148                HasAnyMessageRouteAttribute(method, includeClassRouteAttributes))
 149            {
 2150                context.ReportDiagnostic(Diagnostic.Create(
 2151                    TextOnCallback,
 2152                    location));
 153            }
 154
 3155            context.ReportDiagnostic(Diagnostic.Create(
 3156                MultipleRouteAttributes,
 3157                location,
 3158                displayName));
 3159            return;
 160        }
 161
 86162        if (!IsSupportedReturnType(method.ReturnType))
 163        {
 1164            context.ReportDiagnostic(Diagnostic.Create(
 1165                UnsupportedReturnType,
 1166                location,
 1167                displayName));
 168        }
 169
 86170        ValidateContextParameter(context, method, routeKind.Value, displayName, location);
 86171        ValidateCancellationTokens(context, method, displayName, location);
 86172        ValidateTypedStateAttributes(context, method.ContainingType);
 86173        ValidateTypedStateAttributes(context, method);
 86174        AnalyzeSceneStep(context, method, location);
 86175        AnalyzeAutoAnswerCallback(context, method, routeKind.Value);
 176
 86177        AnalyzeCommandRoutes(context, method, commands, displayName, location, includeClassRouteAttributes);
 86178        AnalyzeTemplateAndRegexRoutes(context, method, routeKind.Value, location, includeClassRouteAttributes);
 86179        AnalyzeBuiltInFilters(context, method, routeKind.Value, location);
 86180        AnalyzeCustomFilters(context, method, routeKind.Value, location);
 86181        AnalyzeChatMemberTransitions(context, method, routeKind.Value, location);
 86182        AnalyzeTelegramRoleRequirements(context, method, location);
 183
 86184        if (routeKind.Value == HandlerKind.Callback &&
 86185            HasAnyMessageRouteAttribute(method, includeClassRouteAttributes))
 186        {
 0187            context.ReportDiagnostic(Diagnostic.Create(
 0188                TextOnCallback,
 0189                location));
 190        }
 191
 86192        if (routeKind.Value == HandlerKind.Callback)
 193        {
 14194            ValidateCallbackRoute(context, method, callbackPayloadType, hasRawCallback, location);
 195        }
 86196    }
 197
 198    private static void AnalyzeAutoAnswerCallback(
 199        SymbolAnalysisContext context,
 200        IMethodSymbol method,
 201        HandlerKind? routeKind)
 202    {
 143203        if (!TelegramHandlerSymbols.HasAttribute(
 143204                method,
 143205                TelegramHandlerSymbols.AutoAnswerCallbackAttribute,
 143206                inherit: true))
 207        {
 141208            return;
 209        }
 210
 2211        if (routeKind == HandlerKind.Callback)
 212        {
 0213            return;
 214        }
 215
 4216        Location? location = method.Locations.FirstOrDefault(static candidate => candidate.IsInSource);
 2217        context.ReportDiagnostic(Diagnostic.Create(
 2218            InvalidAutoAnswerCallback,
 2219            location,
 2220            "AutoAnswerCallbackAttribute can be used only on callback handlers."));
 2221    }
 222
 223}

/_/src/TeleFlow.Generators/TelegramHandlerAnalyzer.Diagnostics.cs

#LineLine coverage
 1using System.Collections.Immutable;
 2using Microsoft.CodeAnalysis;
 3
 4namespace TeleFlow.Generators;
 5
 6public sealed partial class TelegramHandlerAnalyzer
 7{
 8    public const string MultipleRouteAttributesId = "TLF001";
 9    public const string UnsupportedReturnTypeId = "TLF002";
 10    public const string InvalidContextParameterId = "TLF003";
 11    public const string MultipleCancellationTokensId = "TLF004";
 12    public const string InvalidCommandId = "TLF005";
 13    public const string TextOnCallbackId = "TLF006";
 14    public const string DuplicateCommandId = "TLF007";
 15    public const string InvalidHandlerTypeId = "TLF008";
 16    public const string InvalidHandlerMethodId = "TLF009";
 17    public const string InvalidCallbackDataId = "TLF010";
 18    public const string DuplicateCallbackDataPrefixId = "TLF011";
 19    public const string InvalidStateGroupId = "TLF012";
 20    public const string InvalidTypedStateId = "TLF013";
 21    public const string InvalidTypedCallbackId = "TLF014";
 22    public const string InvalidTelegramModuleId = "TLF015";
 23    public const string InvalidRouteTemplateId = "TLF016";
 24    public const string InvalidRouteRegexId = "TLF017";
 25    public const string InvalidRouteValueId = "TLF018";
 26    public const string InvalidFilterId = "TLF019";
 27    public const string InheritedHandlerMethodId = "TLF020";
 28    public const string InvalidCommandPrefixId = "TLF021";
 29    public const string InvalidSceneId = "TLF022";
 30    public const string InvalidSceneStepId = "TLF023";
 31    public const string InvalidAutoAnswerCallbackId = "TLF024";
 32    public const string InvalidClassBasedHandlerId = "TLF025";
 33    public const string InvalidErrorHandlerId = "TLF026";
 34    public const string MissingRouteAttributeId = "TLF027";
 35
 136    private static readonly DiagnosticDescriptor MultipleRouteAttributes = CreateDescriptor(
 137        MultipleRouteAttributesId,
 138        "Telegram handler has multiple route attributes",
 139        "Telegram handler method '{0}' must have exactly one route attribute",
 140        "Usage");
 41
 142    private static readonly DiagnosticDescriptor UnsupportedReturnType = CreateDescriptor(
 143        UnsupportedReturnTypeId,
 144        "Telegram handler has unsupported return type",
 145        "Telegram handler method '{0}' must return Task or ValueTask",
 146        "Usage");
 47
 148    private static readonly DiagnosticDescriptor InvalidContextParameter = CreateDescriptor(
 149        InvalidContextParameterId,
 150        "Telegram handler has invalid context parameter",
 151        "{0} handler method '{1}' must declare exactly one {2} parameter",
 152        "Usage");
 53
 154    private static readonly DiagnosticDescriptor MultipleCancellationTokens = CreateDescriptor(
 155        MultipleCancellationTokensId,
 156        "Telegram handler has multiple CancellationToken parameters",
 157        "Telegram handler method '{0}' can declare at most one CancellationToken parameter",
 158        "Usage");
 59
 160    private static readonly DiagnosticDescriptor InvalidCommand = CreateDescriptor(
 161        InvalidCommandId,
 162        "Telegram command name is invalid",
 163        "CommandAttribute value on '{0}' must be a command name without '/', bot username, whitespace, or empty value",
 164        "Usage");
 65
 166    private static readonly DiagnosticDescriptor TextOnCallback = CreateDescriptor(
 167        TextOnCallbackId,
 168        "Text filter cannot be used on callback handlers",
 169        "TextAttribute can be used only with message or command handlers",
 170        "Usage");
 71
 172    private static readonly DiagnosticDescriptor DuplicateCommand = CreateDescriptor(
 173        DuplicateCommandId,
 174        "Duplicate Telegram command handler",
 175        "Duplicate Telegram command handler registration for command '{0}'",
 176        "Usage");
 77
 178    private static readonly DiagnosticDescriptor InvalidHandlerType = CreateDescriptor(
 179        InvalidHandlerTypeId,
 180        "Telegram handler type is invalid",
 181        "Telegram handler type '{0}' must be a concrete class",
 182        "Usage");
 83
 184    private static readonly DiagnosticDescriptor InvalidHandlerMethod = CreateDescriptor(
 185        InvalidHandlerMethodId,
 186        "Telegram handler method is invalid",
 187        "Telegram handler method '{0}' must be a public instance method",
 188        "Usage");
 89
 190    private static readonly DiagnosticDescriptor InvalidCallbackData = CreateDescriptor(
 191        InvalidCallbackDataId,
 192        "Telegram callback data payload is invalid",
 193        "{0}",
 194        "Usage");
 95
 196    private static readonly DiagnosticDescriptor DuplicateCallbackDataPrefix = CreateDescriptor(
 197        DuplicateCallbackDataPrefixId,
 198        "Duplicate Telegram callback data prefix",
 199        "Duplicate Telegram callback data prefix '{0}'",
 1100        "Usage");
 101
 1102    private static readonly DiagnosticDescriptor InvalidStateGroup = CreateDescriptor(
 1103        InvalidStateGroupId,
 1104        "Telegram state group is invalid",
 1105        "{0}",
 1106        "Usage");
 107
 1108    private static readonly DiagnosticDescriptor InvalidTypedState = CreateDescriptor(
 1109        InvalidTypedStateId,
 1110        "Telegram typed state reference is invalid",
 1111        "{0}",
 1112        "Usage");
 113
 1114    private static readonly DiagnosticDescriptor InvalidTypedCallback = CreateDescriptor(
 1115        InvalidTypedCallbackId,
 1116        "Telegram typed callback handler is invalid",
 1117        "{0}",
 1118        "Usage");
 119
 1120    private static readonly DiagnosticDescriptor InvalidTelegramModule = CreateDescriptor(
 1121        InvalidTelegramModuleId,
 1122        "Telegram module is invalid",
 1123        "{0}",
 1124        "Usage");
 125
 1126    private static readonly DiagnosticDescriptor InvalidRouteTemplate = CreateDescriptor(
 1127        InvalidRouteTemplateId,
 1128        "Telegram route template is invalid",
 1129        "{0}",
 1130        "Usage");
 131
 1132    private static readonly DiagnosticDescriptor InvalidRouteRegex = CreateDescriptor(
 1133        InvalidRouteRegexId,
 1134        "Telegram route regex is invalid",
 1135        "{0}",
 1136        "Usage");
 137
 1138    private static readonly DiagnosticDescriptor InvalidRouteValue = CreateDescriptor(
 1139        InvalidRouteValueId,
 1140        "Telegram route value binding is invalid",
 1141        "{0}",
 1142        "Usage");
 143
 1144    private static readonly DiagnosticDescriptor InvalidFilter = CreateDescriptor(
 1145        InvalidFilterId,
 1146        "Telegram filter usage is invalid",
 1147        "{0}",
 1148        "Usage");
 149
 1150    private static readonly DiagnosticDescriptor InheritedHandlerMethod = CreateDescriptor(
 1151        InheritedHandlerMethodId,
 1152        "Telegram handler method must be declared on the concrete handler type",
 1153        "{0}",
 1154        "Usage");
 155
 1156    private static readonly DiagnosticDescriptor InvalidCommandPrefix = CreateDescriptor(
 1157        InvalidCommandPrefixId,
 1158        "Telegram command prefix is invalid",
 1159        "{0}",
 1160        "Usage");
 161
 1162    private static readonly DiagnosticDescriptor InvalidScene = CreateDescriptor(
 1163        InvalidSceneId,
 1164        "Telegram scene is invalid",
 1165        "{0}",
 1166        "Usage");
 167
 1168    private static readonly DiagnosticDescriptor InvalidSceneStep = CreateDescriptor(
 1169        InvalidSceneStepId,
 1170        "Telegram scene step is invalid",
 1171        "{0}",
 1172        "Usage");
 173
 1174    private static readonly DiagnosticDescriptor InvalidAutoAnswerCallback = CreateDescriptor(
 1175        InvalidAutoAnswerCallbackId,
 1176        "Telegram auto callback answer usage is invalid",
 1177        "{0}",
 1178        "Usage");
 179
 1180    private static readonly DiagnosticDescriptor InvalidClassBasedHandler = CreateDescriptor(
 1181        InvalidClassBasedHandlerId,
 1182        "Telegram class-based handler is invalid",
 1183        "{0}",
 1184        "Usage");
 185
 1186    private static readonly DiagnosticDescriptor InvalidErrorHandler = CreateDescriptor(
 1187        InvalidErrorHandlerId,
 1188        "Telegram error handler is invalid",
 1189        "{0}",
 1190        "Usage");
 191
 1192    private static readonly DiagnosticDescriptor MissingRouteAttribute = CreateDescriptor(
 1193        MissingRouteAttributeId,
 1194        "Telegram handler is missing a route attribute",
 1195        "Telegram handler method '{0}' uses state or filter attributes but has no route attribute. Add [Message], [Comma
 1196        "Usage");
 197
 198    public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } =
 46199        ImmutableArray.Create(
 46200            MultipleRouteAttributes,
 46201            UnsupportedReturnType,
 46202            InvalidContextParameter,
 46203            MultipleCancellationTokens,
 46204            InvalidCommand,
 46205            TextOnCallback,
 46206            DuplicateCommand,
 46207            InvalidHandlerType,
 46208            InvalidHandlerMethod,
 46209            InvalidCallbackData,
 46210            DuplicateCallbackDataPrefix,
 46211            InvalidStateGroup,
 46212            InvalidTypedState,
 46213            InvalidTypedCallback,
 46214            InvalidTelegramModule,
 46215            InvalidRouteTemplate,
 46216            InvalidRouteRegex,
 46217            InvalidRouteValue,
 46218            InvalidFilter,
 46219            InheritedHandlerMethod,
 46220            InvalidCommandPrefix,
 46221            InvalidScene,
 46222            InvalidSceneStep,
 46223            InvalidAutoAnswerCallback,
 46224            InvalidClassBasedHandler,
 46225            InvalidErrorHandler,
 46226            MissingRouteAttribute);
 227
 228    private static DiagnosticDescriptor CreateDescriptor(
 229        string id,
 230        string title,
 231        string messageFormat,
 232        string category)
 233    {
 27234        return new DiagnosticDescriptor(
 27235            id,
 27236            title,
 27237            messageFormat,
 27238            category,
 27239            DiagnosticSeverity.Error,
 27240            isEnabledByDefault: true);
 241    }
 242}

/_/src/TeleFlow.Generators/TelegramHandlerAnalyzer.ErrorHandlers.cs

#LineLine coverage
 1using Microsoft.CodeAnalysis;
 2using Microsoft.CodeAnalysis.Diagnostics;
 3
 4namespace TeleFlow.Generators;
 5
 6public sealed partial class TelegramHandlerAnalyzer
 7{
 8    private static void AnalyzeErrorHandlerMethod(
 9        SymbolAnalysisContext context,
 10        IMethodSymbol method)
 11    {
 14912        IReadOnlyList<AttributeData> errorAttributes = TelegramHandlerSymbols.GetErrorAttributes(method, inherit: true);
 13
 14914        if (errorAttributes.Count == 0)
 15        {
 13916            return;
 17        }
 18
 2019        Location? location = method.Locations.FirstOrDefault(static candidate => candidate.IsInSource);
 1020        string displayName = method.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat);
 21
 1022        if (method.ContainingType.TypeKind is TypeKind.Interface ||
 1023            method.ContainingType.IsAbstract ||
 1024            method.DeclaredAccessibility != Accessibility.Public ||
 1025            method.IsStatic ||
 1026            method.IsGenericMethod)
 27        {
 228            ReportInvalidErrorHandler(
 229                context,
 230                location,
 231                $"Telegram error handler method '{displayName}' must be a public instance method on a concrete class.");
 32        }
 33
 1034        if (!IsSupportedErrorHandlerReturnType(method.ReturnType))
 35        {
 136            ReportInvalidErrorHandler(
 137                context,
 138                location,
 139                $"Telegram error handler method '{displayName}' must return TelegramErrorHandlingResult, Task<TelegramEr
 40        }
 41
 1042        List<ITypeSymbol?> exceptionTypes = GetErrorHandlerExceptionTypes(context, errorAttributes, location);
 43
 1044        ValidateErrorHandlerParameters(
 1045            context,
 1046            method,
 1047            exceptionTypes,
 1048            location);
 1049    }
 50
 51    private static List<ITypeSymbol?> GetErrorHandlerExceptionTypes(
 52        SymbolAnalysisContext context,
 53        IReadOnlyList<AttributeData> attributes,
 54        Location? location)
 55    {
 1056        List<ITypeSymbol?> exceptionTypes = new List<ITypeSymbol?>();
 57
 4058        foreach (AttributeData attribute in attributes)
 59        {
 1060            if (!TelegramHandlerSymbols.IsGenericAttribute(attribute, TelegramHandlerSymbols.GenericErrorAttribute))
 61            {
 962                exceptionTypes.Add(null);
 963                continue;
 64            }
 65
 166            if (attribute.AttributeClass is not INamedTypeSymbol { TypeArguments.Length: 1 } genericAttribute ||
 167                !IsExceptionType(genericAttribute.TypeArguments[0]))
 68            {
 069                ReportInvalidErrorHandler(
 070                    context,
 071                    location,
 072                    "ErrorAttribute<TException> generic argument must derive from Exception.");
 073                continue;
 74            }
 75
 176            exceptionTypes.Add(genericAttribute.TypeArguments[0]);
 77        }
 78
 1079        return exceptionTypes;
 80    }
 81
 82    private static void ValidateErrorHandlerParameters(
 83        SymbolAnalysisContext context,
 84        IMethodSymbol method,
 85        IReadOnlyList<ITypeSymbol?> exceptionTypes,
 86        Location? location)
 87    {
 1088        if (method.Parameters.Count(static parameter =>
 2489                TelegramHandlerSymbols.IsType(parameter.Type, TelegramHandlerSymbols.TelegramErrorContext)) > 1)
 90        {
 091            ReportInvalidErrorHandler(
 092                context,
 093                location,
 094                "A Telegram error handler method can declare at most one TelegramErrorContext parameter.");
 95        }
 96
 1097        if (method.Parameters.Count(static parameter =>
 2498                TelegramHandlerSymbols.IsType(parameter.Type, TelegramHandlerSymbols.CancellationToken)) > 1)
 99        {
 1100            ReportInvalidErrorHandler(
 1101                context,
 1102                location,
 1103                "A Telegram error handler method can declare at most one CancellationToken parameter.");
 104        }
 105
 10106        IParameterSymbol[] exceptionParameters = method.Parameters
 14107            .Where(static parameter => IsExceptionType(parameter.Type))
 10108            .ToArray();
 109
 10110        if (exceptionParameters.Length > 1)
 111        {
 1112            ReportInvalidErrorHandler(
 1113                context,
 1114                location,
 1115                "A Telegram error handler method can declare at most one exception parameter.");
 116        }
 117
 10118        IParameterSymbol[] telegramContextParameters = method.Parameters
 14119            .Where(static parameter => IsTelegramContextType(parameter.Type))
 10120            .ToArray();
 121
 10122        if (telegramContextParameters.Length > 1)
 123        {
 1124            ReportInvalidErrorHandler(
 1125                context,
 1126                location,
 1127                "A Telegram error handler method can declare at most one Telegram context parameter.");
 128        }
 129
 10130        IParameterSymbol? exceptionParameter = exceptionParameters.FirstOrDefault();
 131
 10132        if (exceptionParameter is null)
 133        {
 1134            return;
 135        }
 136
 36137        foreach (ITypeSymbol? exceptionType in exceptionTypes)
 138        {
 9139            if (exceptionType is null)
 140            {
 8141                if (!TelegramHandlerSymbols.IsType(exceptionParameter.Type, TelegramHandlerSymbols.Exception))
 142                {
 1143                    ReportInvalidErrorHandler(
 1144                        context,
 1145                        location,
 1146                        "Catch-all ErrorAttribute methods must bind exception parameters as Exception.");
 147                }
 148
 1149                continue;
 150            }
 151
 1152            if (!IsAssignableFrom(exceptionParameter.Type, exceptionType))
 153            {
 1154                ReportInvalidErrorHandler(
 1155                    context,
 1156                    location,
 1157                    $"Exception parameter '{exceptionParameter.Name}' must be assignable from {exceptionType.Name}.");
 158            }
 159        }
 9160    }
 161
 162    private static bool IsSupportedErrorHandlerReturnType(ITypeSymbol returnType)
 163    {
 10164        if (TelegramHandlerSymbols.IsType(returnType, TelegramHandlerSymbols.TelegramErrorHandlingResult))
 165        {
 9166            return true;
 167        }
 168
 1169        return returnType is INamedTypeSymbol
 1170        {
 1171            IsGenericType: true,
 1172            TypeArguments.Length: 1
 1173        } namedType &&
 1174        TelegramHandlerSymbols.IsType(namedType.TypeArguments[0], TelegramHandlerSymbols.TelegramErrorHandlingResult) &&
 1175        namedType.ConstructedFrom.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat) is
 1176            "System.Threading.Tasks.Task<TResult>" or
 1177            "System.Threading.Tasks.ValueTask<TResult>";
 178    }
 179
 180    private static bool IsExceptionType(ITypeSymbol type)
 181    {
 15182        return type is INamedTypeSymbol namedType &&
 15183               IsAssignableTo(namedType, TelegramHandlerSymbols.Exception);
 184    }
 185
 186    private static bool IsTelegramContextType(ITypeSymbol type)
 187    {
 14188        return type is INamedTypeSymbol namedType &&
 14189               IsAssignableTo(namedType, TelegramHandlerSymbols.TelegramUpdateContext);
 190    }
 191
 192    private static bool IsAssignableFrom(ITypeSymbol targetType, ITypeSymbol sourceType)
 193    {
 1194        if (sourceType is not INamedTypeSymbol source)
 195        {
 0196            return false;
 197        }
 198
 10199        for (INamedTypeSymbol? current = source; current is not null; current = current.BaseType)
 200        {
 4201            if (SymbolEqualityComparer.Default.Equals(current, targetType))
 202            {
 0203                return true;
 204            }
 205        }
 206
 1207        return false;
 208    }
 209
 210    private static void ReportInvalidErrorHandler(
 211        SymbolAnalysisContext context,
 212        Location? location,
 213        string message)
 214    {
 8215        context.ReportDiagnostic(Diagnostic.Create(
 8216            InvalidErrorHandler,
 8217            location,
 8218            message));
 8219    }
 220}

/_/src/TeleFlow.Generators/TelegramHandlerAnalyzer.Inheritance.cs

#LineLine coverage
 1using Microsoft.CodeAnalysis;
 2using Microsoft.CodeAnalysis.Diagnostics;
 3
 4namespace TeleFlow.Generators;
 5
 6public sealed partial class TelegramHandlerAnalyzer
 7{
 8    private static void AnalyzeInheritedHandlerMethods(
 9        SymbolAnalysisContext context,
 10        INamedTypeSymbol type)
 11    {
 9912        if (!IsConcreteHandlerCandidate(type))
 13        {
 4114            return;
 15        }
 16
 5817        IMethodSymbol[] inheritedHandlerMethods = GetInheritedHandlerMethods(type)
 518            .Where(method => !IsOverriddenBy(type, method))
 319            .GroupBy(static method => method.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat), StringCompar
 320            .Select(static group => group.First())
 5821            .ToArray();
 22
 12223        foreach (IMethodSymbol method in inheritedHandlerMethods)
 24        {
 325            context.ReportDiagnostic(Diagnostic.Create(
 326                InheritedHandlerMethod,
 327                type.Locations.FirstOrDefault(static candidate => candidate.IsInSource),
 328                $"Telegram handler type '{type.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)}' inherits 
 29        }
 5830    }
 31
 32    private static IEnumerable<IMethodSymbol> GetInheritedHandlerMethods(INamedTypeSymbol type)
 33    {
 26634        for (INamedTypeSymbol? current = type.BaseType; current is not null; current = current.BaseType)
 35        {
 123836            foreach (IMethodSymbol method in current.GetMembers().OfType<IMethodSymbol>())
 37            {
 54438                if (method is not
 54439                    {
 54440                        DeclaredAccessibility: Accessibility.Public,
 54441                        IsStatic: false,
 54442                        IsGenericMethod: false
 54443                    } ||
 54444                    method.MethodKind != MethodKind.Ordinary ||
 54445                    (!TelegramHandlerSymbols.HasAnyRouteAttribute(method) &&
 54446                     !TelegramHandlerSymbols.HasAnyErrorAttribute(method)))
 47                {
 48                    continue;
 49                }
 50
 551                yield return method;
 52            }
 53        }
 5854    }
 55
 56    private static bool IsConcreteHandlerCandidate(INamedTypeSymbol type)
 57    {
 9958        if (type.TypeKind is TypeKind.Interface || type.IsAbstract)
 59        {
 960            return false;
 61        }
 62
 9063        if (TelegramHandlerSymbols.HasAttribute(type, TelegramHandlerSymbols.TelegramModuleAttribute))
 64        {
 565            return true;
 66        }
 67
 8568        if (IsClassBasedHandlerType(type) &&
 8569            (TelegramHandlerSymbols.HasAnyRouteAttribute(type) ||
 8670             GetDeclaredHandleAsyncMethods(type).Any(static method => TelegramHandlerSymbols.HasAnyRouteAttribute(method
 71        {
 972            return true;
 73        }
 74
 7675        return type.GetMembers()
 7676            .OfType<IMethodSymbol>()
 7677            .Any(static method =>
 30278                method.DeclaredAccessibility == Accessibility.Public &&
 30279                !method.IsStatic &&
 30280                method.MethodKind == MethodKind.Ordinary &&
 30281                !method.IsGenericMethod &&
 30282                (TelegramHandlerSymbols.HasAnyRouteAttribute(method) ||
 30283                 TelegramHandlerSymbols.HasAnyErrorAttribute(method)));
 84    }
 85
 86    private static bool IsOverriddenBy(
 87        INamedTypeSymbol type,
 88        IMethodSymbol inheritedMethod)
 89    {
 2090        foreach (IMethodSymbol method in type.GetMembers().OfType<IMethodSymbol>())
 91        {
 1292            for (IMethodSymbol? current = method.OverriddenMethod; current is not null; current = current.OverriddenMeth
 93            {
 294                if (SymbolEqualityComparer.Default.Equals(
 295                        current.OriginalDefinition,
 296                        inheritedMethod.OriginalDefinition))
 97                {
 298                    return true;
 99                }
 100            }
 101        }
 102
 3103        return false;
 2104    }
 105}

/_/src/TeleFlow.Generators/TelegramHandlerAnalyzer.Modules.cs

#LineLine coverage
 1using Microsoft.CodeAnalysis;
 2using Microsoft.CodeAnalysis.Diagnostics;
 3
 4namespace TeleFlow.Generators;
 5
 6public sealed partial class TelegramHandlerAnalyzer
 7{
 8    private static void AnalyzeTelegramModuleType(SymbolAnalysisContext context, INamedTypeSymbol type)
 9    {
 9910        if (!TelegramHandlerSymbols.HasAttribute(type, TelegramHandlerSymbols.TelegramModuleAttribute))
 11        {
 9312            return;
 13        }
 14
 1215        Location? location = type.Locations.FirstOrDefault(static candidate => candidate.IsInSource);
 616        string displayName = type.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat);
 17
 618        if (type.TypeKind is TypeKind.Interface || type.IsAbstract || type.IsStatic)
 19        {
 220            context.ReportDiagnostic(Diagnostic.Create(
 221                InvalidTelegramModule,
 222                location,
 223                $"Telegram module type '{displayName}' must be a concrete class."));
 24        }
 25
 626        if (!HasTelegramHandlerMethods(type))
 27        {
 428            context.ReportDiagnostic(Diagnostic.Create(
 429                InvalidTelegramModule,
 430                location,
 431                $"Telegram module type '{displayName}' must contain at least one Telegram handler method."));
 32        }
 633    }
 34
 35    private static bool HasTelegramHandlerMethods(INamedTypeSymbol type)
 36    {
 637        return type.GetMembers()
 638            .OfType<IMethodSymbol>()
 639            .Any(static method =>
 1140                method.DeclaredAccessibility == Accessibility.Public &&
 1141                !method.IsStatic &&
 1142                (TelegramHandlerSymbols.HasAnyRouteAttribute(method) ||
 1143                 TelegramHandlerSymbols.HasAnyErrorAttribute(method)));
 44    }
 45}

/_/src/TeleFlow.Generators/TelegramHandlerAnalyzer.RouteLessHandlers.cs

#LineLine coverage
 1using Microsoft.CodeAnalysis;
 2using Microsoft.CodeAnalysis.Diagnostics;
 3
 4namespace TeleFlow.Generators;
 5
 6public sealed partial class TelegramHandlerAnalyzer
 7{
 18    private static readonly string[] RouteLessHandlerAttributeMetadataNames =
 19    {
 110        TelegramHandlerSymbols.StateAttribute,
 111        TelegramHandlerSymbols.RequireTelegramRoleAttribute,
 112        TelegramHandlerSymbols.ChatMemberTransitionAttribute,
 113        TelegramHandlerSymbols.ChatMemberChangedAttribute
 114    };
 15
 116    private static readonly string[] RouteLessHandlerGenericAttributeMetadataNames =
 117    {
 118        TelegramHandlerSymbols.GenericStateAttribute,
 119        TelegramHandlerSymbols.GenericUseFilterAttribute
 120    };
 21
 22    private static void AnalyzeMissingRouteAttribute(
 23        SymbolAnalysisContext context,
 24        IMethodSymbol method)
 25    {
 5726        if (TelegramHandlerSymbols.HasAnyErrorAttribute(method) ||
 5727            TelegramHandlerSymbols.HasAttribute(method, TelegramHandlerSymbols.SceneStepAttribute, inherit: true) ||
 5728            !TryGetRouteLessHandlerAttribute(method, out AttributeData attribute))
 29        {
 4930            return;
 31        }
 32
 833        string displayName = method.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat);
 834        Location? location = attribute.ApplicationSyntaxReference
 835            ?.GetSyntax(context.CancellationToken)
 836            .GetLocation();
 37
 838        context.ReportDiagnostic(Diagnostic.Create(
 839            MissingRouteAttribute,
 040            location ?? method.Locations.FirstOrDefault(static candidate => candidate.IsInSource),
 841            displayName));
 842    }
 43
 44    private static bool TryGetRouteLessHandlerAttribute(
 45        IMethodSymbol method,
 46        out AttributeData attribute)
 47    {
 4648        if (TryGetFirstKnownAttribute(method, RouteLessHandlerAttributeMetadataNames, out attribute) ||
 4649            TryGetFirstKnownGenericAttribute(method, RouteLessHandlerGenericAttributeMetadataNames, out attribute) ||
 4650            TryGetFirst(TelegramHandlerSymbols.GetTelegramFilterAttributes(method, inherit: true), out attribute) ||
 4651            TryGetFirst(TelegramBuiltInFilterFacts.GetAttributes(method), out attribute))
 52        {
 853            return true;
 54        }
 55
 3856        attribute = null!;
 3857        return false;
 58    }
 59
 60    private static bool TryGetFirstKnownAttribute(
 61        IMethodSymbol method,
 62        IReadOnlyList<string> metadataNames,
 63        out AttributeData attribute)
 64    {
 44565        foreach (string metadataName in metadataNames)
 66        {
 17867            if (TryGetFirst(TelegramHandlerSymbols.GetAttributes(method, metadataName, inherit: true), out attribute))
 68            {
 369                return true;
 70            }
 71        }
 72
 4373        attribute = null!;
 4374        return false;
 375    }
 76
 77    private static bool TryGetFirstKnownGenericAttribute(
 78        IMethodSymbol method,
 79        IReadOnlyList<string> metadataNames,
 80        out AttributeData attribute)
 81    {
 25482        foreach (string metadataName in metadataNames)
 83        {
 8584            if (TryGetFirst(TelegramHandlerSymbols.GetGenericAttributes(method, metadataName, inherit: true), out attrib
 85            {
 286                return true;
 87            }
 88        }
 89
 4190        attribute = null!;
 4191        return false;
 292    }
 93
 94    private static bool TryGetFirst(
 95        IEnumerable<AttributeData> attributes,
 96        out AttributeData attribute)
 97    {
 69698        foreach (AttributeData candidate in attributes)
 99        {
 8100            attribute = candidate;
 8101            return true;
 102        }
 103
 336104        attribute = null!;
 336105        return false;
 8106    }
 107}

/_/src/TeleFlow.Generators/TelegramHandlerAnalyzer.RouteValidation.cs

#LineLine coverage
 1using System.Collections.Concurrent;
 2using System.Text.RegularExpressions;
 3using Microsoft.CodeAnalysis;
 4using Microsoft.CodeAnalysis.Diagnostics;
 5
 6namespace TeleFlow.Generators;
 7
 8public sealed partial class TelegramHandlerAnalyzer
 9{
 10    private const int CommandPrefixModeRequired = 0;
 11    private const int CommandPrefixModeOptional = 1;
 12    private const int CommandPrefixModeNoPrefix = 2;
 13
 14    private static HandlerKind? GetRouteKind(
 15        IMethodSymbol method,
 16        bool includeClassRouteAttributes,
 17        out ITypeSymbol? callbackPayloadType,
 18        out bool hasRawCallback,
 19        out bool hasMixedCallbackRoutes)
 20    {
 9721        callbackPayloadType = null;
 9722        hasRawCallback = GetRouteAttributes(
 9723            method,
 9724            TelegramHandlerSymbols.CallbackAttribute,
 9725            includeClassRouteAttributes).Count > 0;
 26
 9727        IReadOnlyList<AttributeData> genericCallbackAttributes = GetGenericRouteAttributes(
 9728            method,
 9729            TelegramHandlerSymbols.GenericCallbackAttribute,
 9730            includeClassRouteAttributes);
 9731        AttributeData? genericCallback = genericCallbackAttributes.Count > 0
 9732            ? genericCallbackAttributes[0]
 9733            : null;
 34
 9735        if (genericCallback?.AttributeClass is INamedTypeSymbol { TypeArguments.Length: 1 } genericCallbackType)
 36        {
 937            callbackPayloadType = genericCallbackType.TypeArguments[0];
 38        }
 39
 9740        bool hasCallback = hasRawCallback || callbackPayloadType is not null;
 9741        int messageRouteCount = GetMessageRouteAttributeCount(method, includeClassRouteAttributes);
 9742        int chatMemberRouteCount = GetChatMemberRouteAttributeCount(method, includeClassRouteAttributes);
 9743        hasMixedCallbackRoutes = (hasRawCallback && callbackPayloadType is not null) ||
 9744                                 (hasCallback && (messageRouteCount > 0 || chatMemberRouteCount > 0)) ||
 9745                                 (chatMemberRouteCount > 0 && messageRouteCount > 0);
 46
 9747        if (hasMixedCallbackRoutes)
 48        {
 349            return null;
 50        }
 51
 9452        if (hasCallback)
 53        {
 1854            return HandlerKind.Callback;
 55        }
 56
 7657        if (chatMemberRouteCount > 0)
 58        {
 1159            return HandlerKind.ChatMember;
 60        }
 61
 6562        return messageRouteCount > 0 ? HandlerKind.Message : null;
 63    }
 64
 65    private static int GetMessageRouteAttributeCount(
 66        IMethodSymbol method,
 67        bool includeClassRouteAttributes = false)
 68    {
 11969        return GetRouteAttributes(method, TelegramHandlerSymbols.CommandAttribute, includeClassRouteAttributes).Count +
 11970               GetRouteAttributes(method, TelegramHandlerSymbols.MessageAttribute, includeClassRouteAttributes).Count +
 11971               GetRouteAttributes(method, TelegramHandlerSymbols.TextAttribute, includeClassRouteAttributes).Count +
 11972               GetRouteAttributes(method, TelegramHandlerSymbols.TextTemplateAttribute, includeClassRouteAttributes).Cou
 11973               GetRouteAttributes(method, TelegramHandlerSymbols.CommandTemplateAttribute, includeClassRouteAttributes).
 11974               GetRouteAttributes(method, TelegramHandlerSymbols.TextRegexAttribute, includeClassRouteAttributes).Count 
 11975               GetRouteAttributes(method, TelegramHandlerSymbols.CommandRegexAttribute, includeClassRouteAttributes).Cou
 76    }
 77
 78    private static bool HasAnyMessageRouteAttribute(
 79        IMethodSymbol method,
 80        bool includeClassRouteAttributes = false)
 81    {
 1682        return GetMessageRouteAttributeCount(method, includeClassRouteAttributes) > 0;
 83    }
 84
 85    private static int GetChatMemberRouteAttributeCount(
 86        IMethodSymbol method,
 87        bool includeClassRouteAttributes = false)
 88    {
 9789        return GetRouteAttributes(method, TelegramHandlerSymbols.ChatMemberUpdatedAttribute, includeClassRouteAttributes
 9790               GetRouteAttributes(method, TelegramHandlerSymbols.MyChatMemberUpdatedAttribute, includeClassRouteAttribut
 91    }
 92
 93    private static void AnalyzeCommandRoutes(
 94        SymbolAnalysisContext context,
 95        IMethodSymbol method,
 96        ConcurrentBag<CommandRegistration> commands,
 97        string displayName,
 98        Location? location,
 99        bool includeClassRouteAttributes = false)
 100    {
 184101        foreach (AttributeData attribute in GetRouteAttributes(
 86102                     method,
 86103                     TelegramHandlerSymbols.CommandAttribute,
 86104                     includeClassRouteAttributes))
 105        {
 6106            string? command = GetConstructorString(attribute);
 107
 6108            if (!IsValidCommand(command))
 109            {
 1110                context.ReportDiagnostic(Diagnostic.Create(
 1111                    InvalidCommand,
 1112                    location,
 1113                    displayName));
 1114                continue;
 115            }
 116
 5117            if (!TryGetPrefixes(attribute, out IReadOnlyList<string> prefixes, out string prefixReason))
 118            {
 1119                context.ReportDiagnostic(Diagnostic.Create(
 1120                    InvalidCommandPrefix,
 1121                    location,
 1122                    prefixReason));
 1123                continue;
 124            }
 125
 4126            int prefixMode = GetCommandPrefixMode(attribute);
 127
 4128            if (!TryValidateCommandPrefixMode(attribute, prefixMode, out string prefixModeReason))
 129            {
 0130                context.ReportDiagnostic(Diagnostic.Create(
 0131                    InvalidCommandPrefix,
 0132                    location,
 0133                    prefixModeReason));
 0134                continue;
 135            }
 136
 4137            bool allowSpace = GetNamedBool(attribute, "AllowSpaceAfterPrefix", defaultValue: false);
 138
 4139            if (prefixMode is CommandPrefixModeRequired or CommandPrefixModeOptional)
 140            {
 16141                foreach (string prefix in prefixes)
 142                {
 4143                    commands.Add(new CommandRegistration(
 4144                        $"{prefix.ToUpperInvariant()}\u001F{allowSpace}\u001FPREFIXED\u001F{command!.ToUpperInvariant()}
 4145                        $"{prefix}{command}",
 4146                        location));
 147                }
 148            }
 149
 4150            if (prefixMode is CommandPrefixModeOptional or CommandPrefixModeNoPrefix)
 151            {
 0152                commands.Add(new CommandRegistration(
 0153                    $"NO_PREFIX\u001F{command!.ToUpperInvariant()}",
 0154                    command,
 0155                    location));
 156            }
 157        }
 86158    }
 159
 160    private static void AnalyzeTemplateAndRegexRoutes(
 161        SymbolAnalysisContext context,
 162        IMethodSymbol method,
 163        HandlerKind routeKind,
 164        Location? location,
 165        bool includeClassRouteAttributes = false)
 166    {
 86167        if (routeKind is HandlerKind.Callback or HandlerKind.ChatMember)
 168        {
 24169            return;
 170        }
 171
 62172        List<IReadOnlyDictionary<string, RouteValueDefinition>> routeValues = new List<IReadOnlyDictionary<string, Route
 173
 142174        foreach (AttributeData attribute in GetRouteAttributes(
 62175                     method,
 62176                     TelegramHandlerSymbols.TextTemplateAttribute,
 62177                     includeClassRouteAttributes))
 178        {
 9179            string? template = GetConstructorString(attribute);
 180
 9181            if (!TryParseRouteTemplate(template, out IReadOnlyDictionary<string, RouteValueDefinition> values, out strin
 182            {
 4183                context.ReportDiagnostic(Diagnostic.Create(
 4184                    InvalidRouteTemplate,
 4185                    location,
 4186                    reason));
 4187                continue;
 188            }
 189
 5190            routeValues.Add(values);
 191        }
 192
 130193        foreach (AttributeData attribute in GetRouteAttributes(
 62194                     method,
 62195                     TelegramHandlerSymbols.CommandTemplateAttribute,
 62196                     includeClassRouteAttributes))
 197        {
 3198            string? template = GetConstructorString(attribute);
 199
 3200            if (!TryParseRouteTemplate(template, out IReadOnlyDictionary<string, RouteValueDefinition> values, out strin
 201            {
 0202                context.ReportDiagnostic(Diagnostic.Create(
 0203                    InvalidRouteTemplate,
 0204                    location,
 0205                    reason));
 0206                continue;
 207            }
 208
 3209            if (!TryGetPrefixes(attribute, out IReadOnlyList<string> prefixes, out string prefixReason))
 210            {
 0211                context.ReportDiagnostic(Diagnostic.Create(
 0212                    InvalidCommandPrefix,
 0213                    location,
 0214                    prefixReason));
 0215                continue;
 216            }
 217
 3218            int prefixMode = GetCommandPrefixMode(attribute);
 219
 3220            if (!TryValidateCommandPrefixMode(attribute, prefixMode, out string prefixModeReason))
 221            {
 1222                context.ReportDiagnostic(Diagnostic.Create(
 1223                    InvalidCommandPrefix,
 1224                    location,
 1225                    prefixModeReason));
 1226                continue;
 227            }
 228
 2229            if (CommandPatternStartsWithPrefix(template!, prefixes))
 230            {
 2231                context.ReportDiagnostic(Diagnostic.Create(
 2232                    InvalidRouteTemplate,
 2233                    location,
 2234                    "Command route template value must not include a command prefix."));
 2235                continue;
 236            }
 237
 0238            routeValues.Add(values);
 239        }
 240
 128241        foreach (AttributeData attribute in GetRouteAttributes(
 62242                     method,
 62243                     TelegramHandlerSymbols.TextRegexAttribute,
 62244                     includeClassRouteAttributes))
 245        {
 2246            string? pattern = GetConstructorString(attribute);
 247
 2248            if (!TryParseRouteRegex(pattern, out IReadOnlyDictionary<string, RouteValueDefinition> values, out string re
 249            {
 1250                context.ReportDiagnostic(Diagnostic.Create(
 1251                    InvalidRouteRegex,
 1252                    location,
 1253                    reason));
 1254                continue;
 255            }
 256
 1257            routeValues.Add(values);
 258        }
 259
 126260        foreach (AttributeData attribute in GetRouteAttributes(
 62261                     method,
 62262                     TelegramHandlerSymbols.CommandRegexAttribute,
 62263                     includeClassRouteAttributes))
 264        {
 1265            string? pattern = GetConstructorString(attribute);
 266
 1267            if (!TryParseRouteRegex(pattern, out IReadOnlyDictionary<string, RouteValueDefinition> values, out string re
 268            {
 0269                context.ReportDiagnostic(Diagnostic.Create(
 0270                    InvalidRouteRegex,
 0271                    location,
 0272                    reason));
 0273                continue;
 274            }
 275
 1276            if (!TryGetPrefixes(attribute, out _, out string prefixReason))
 277            {
 1278                context.ReportDiagnostic(Diagnostic.Create(
 1279                    InvalidCommandPrefix,
 1280                    location,
 1281                    prefixReason));
 1282                continue;
 283            }
 284
 0285            int prefixMode = GetCommandPrefixMode(attribute);
 286
 0287            if (!TryValidateCommandPrefixMode(attribute, prefixMode, out string prefixModeReason))
 288            {
 0289                context.ReportDiagnostic(Diagnostic.Create(
 0290                    InvalidCommandPrefix,
 0291                    location,
 0292                    prefixModeReason));
 0293                continue;
 294            }
 295
 0296            routeValues.Add(values);
 297        }
 298
 62299        ValidateRouteValueParameters(context, method, routeValues, location, includeClassRouteAttributes);
 62300    }
 301
 302    private static void ValidateRouteValueParameters(
 303        SymbolAnalysisContext context,
 304        IMethodSymbol method,
 305        IReadOnlyList<IReadOnlyDictionary<string, RouteValueDefinition>> routeValues,
 306        Location? location,
 307        bool includeClassRouteAttributes = false)
 308    {
 62309        if (routeValues.Count == 0)
 310        {
 56311            return;
 312        }
 313
 6314        string[] firstNames = routeValues[0].Keys.OrderBy(static name => name, StringComparer.Ordinal).ToArray();
 315
 6316        if (GetMessageRouteAttributeCount(method, includeClassRouteAttributes) > routeValues.Count)
 317        {
 0318            context.ReportDiagnostic(Diagnostic.Create(
 0319                InvalidRouteValue,
 0320                location,
 0321                "A handler method with route value parameters cannot also have routes that do not provide route values."
 0322            return;
 323        }
 324
 12325        foreach (IReadOnlyDictionary<string, RouteValueDefinition> values in routeValues.Skip(1))
 326        {
 0327            string[] names = values.Keys.OrderBy(static name => name, StringComparer.Ordinal).ToArray();
 328
 0329            if (!firstNames.SequenceEqual(names, StringComparer.Ordinal))
 330            {
 0331                context.ReportDiagnostic(Diagnostic.Create(
 0332                    InvalidRouteValue,
 0333                    location,
 0334                    "Multiple route attributes on one handler method must expose the same route value names."));
 0335                return;
 336            }
 337        }
 338
 24339        foreach (string name in firstNames)
 340        {
 16341            IParameterSymbol? parameter = method.Parameters.FirstOrDefault(parameter => string.Equals(parameter.Name, na
 342
 6343            if (parameter is null)
 344            {
 2345                context.ReportDiagnostic(Diagnostic.Create(
 2346                    InvalidRouteValue,
 2347                    location,
 2348                    $"Route value '{name}' must have a matching handler parameter with the same name."));
 2349                continue;
 350            }
 351
 4352            if (!IsSupportedRouteValueParameterType(parameter.Type))
 353            {
 1354                context.ReportDiagnostic(Diagnostic.Create(
 1355                    InvalidRouteValue,
 1356                    parameter.Locations.FirstOrDefault(static candidate => candidate.IsInSource) ?? location,
 1357                    $"Route value parameter '{name}' must be string, string?, int, int?, long, or long?."));
 1358                continue;
 359            }
 360
 12361            foreach (IReadOnlyDictionary<string, RouteValueDefinition> values in routeValues)
 362            {
 3363                RouteValueDefinition routeValue = values[name];
 364
 3365                if (routeValue.IsOptional != IsNullableRouteValueParameter(parameter.Type))
 366                {
 3367                    string expectedNullability = routeValue.IsOptional ? "nullable" : "non-nullable";
 3368                    context.ReportDiagnostic(Diagnostic.Create(
 3369                        InvalidRouteValue,
 3370                        parameter.Locations.FirstOrDefault(static candidate => candidate.IsInSource) ?? location,
 3371                        $"Route value parameter '{name}' must be {expectedNullability} for the selected route template."
 3372                    continue;
 373                }
 374
 0375                if (routeValue.Constraint is { } constraint &&
 0376                    !RouteConstraintMatchesParameter(constraint, parameter.Type))
 377                {
 0378                    context.ReportDiagnostic(Diagnostic.Create(
 0379                        InvalidRouteValue,
 0380                        parameter.Locations.FirstOrDefault(static candidate => candidate.IsInSource) ?? location,
 0381                        $"Route value parameter '{name}' does not match route constraint '{constraint}'."));
 382                }
 383            }
 384        }
 6385    }
 386
 387    private static void AnalyzeBuiltInFilters(
 388        SymbolAnalysisContext context,
 389        IMethodSymbol method,
 390        HandlerKind routeKind,
 391        Location? location)
 392    {
 86393        TelegramHandlerMetadataRouteKind metadataRouteKind = ToMetadataRouteKind(routeKind);
 86394        AttributeData[] filterAttributes = TelegramBuiltInFilterFacts.GetAttributes(method.ContainingType)
 86395            .Concat(TelegramBuiltInFilterFacts.GetAttributes(method))
 86396            .ToArray();
 397
 233398        foreach (AttributeData attribute in filterAttributes)
 399        {
 34400            if (!TelegramBuiltInFilterFacts.TryGetSpec(attribute, out TelegramBuiltInFilterSpec spec))
 401            {
 402                continue;
 403            }
 404
 34405            if (!TelegramBuiltInFilterFacts.SupportsRouteKind(spec.Target, metadataRouteKind))
 406            {
 7407                context.ReportDiagnostic(Diagnostic.Create(
 7408                    InvalidFilter,
 7409                    location,
 7410                    TelegramBuiltInFilterFacts.GetInvalidFilterMessage(spec.Target, metadataRouteKind)));
 7411                return;
 412            }
 413        }
 414
 79415        AnalyzeChatTypeFilters(
 79416            context,
 79417            method,
 79418            TelegramHandlerSymbols.ChatTypeAttribute,
 79419            "ChatTypeAttribute",
 79420            location);
 79421        AnalyzeChatTypeFilters(
 79422            context,
 79423            method,
 79424            TelegramHandlerSymbols.SenderChatTypeAttribute,
 79425            "SenderChatTypeAttribute",
 79426            location);
 427
 164428        foreach (AttributeData attribute in TelegramHandlerSymbols.GetAttributes(method.ContainingType, TelegramHandlerS
 79429                     .Concat(TelegramHandlerSymbols.GetAttributes(method, TelegramHandlerSymbols.ChatIdAttribute, inheri
 430        {
 3431            if (attribute.ConstructorArguments.Length == 0 ||
 3432                attribute.ConstructorArguments[0].Values.IsDefaultOrEmpty ||
 6433                attribute.ConstructorArguments[0].Values.Any(static value => value.Value is not long chatId || chatId ==
 434            {
 1435                context.ReportDiagnostic(Diagnostic.Create(
 1436                    InvalidFilter,
 1437                    location,
 1438                    "ChatIdAttribute must specify at least one non-zero Telegram chat id."));
 439            }
 440        }
 441
 164442        foreach (AttributeData attribute in TelegramHandlerSymbols.GetAttributes(method.ContainingType, TelegramHandlerS
 79443                     .Concat(TelegramHandlerSymbols.GetAttributes(method, TelegramHandlerSymbols.ChatUsernameAttribute, 
 444        {
 3445            if (attribute.ConstructorArguments.Length == 0 ||
 3446                attribute.ConstructorArguments[0].Values.IsDefaultOrEmpty ||
 6447                attribute.ConstructorArguments[0].Values.Any(static value => value.Value is not string username || Canon
 448            {
 1449                context.ReportDiagnostic(Diagnostic.Create(
 1450                    InvalidFilter,
 1451                    location,
 1452                    "ChatUsernameAttribute must specify at least one non-empty Telegram chat username."));
 453            }
 454        }
 455
 79456        AnalyzeSenderIdFilters(
 79457            context,
 79458            method,
 79459            TelegramHandlerSymbols.FromUserAttribute,
 79460            "FromUserAttribute",
 79461            "user",
 79462            location);
 79463        AnalyzeSenderIdFilters(
 79464            context,
 79465            method,
 79466            TelegramHandlerSymbols.FromBotAttribute,
 79467            "FromBotAttribute",
 79468            "bot",
 79469            location);
 470
 162471        foreach (AttributeData attribute in TelegramHandlerSymbols.GetAttributes(method.ContainingType, TelegramHandlerS
 79472                     .Concat(TelegramHandlerSymbols.GetAttributes(method, TelegramHandlerSymbols.CallbackDataPrefixAttri
 473        {
 2474            if (attribute.ConstructorArguments.Length == 0 ||
 2475                attribute.ConstructorArguments[0].Value is not string prefix ||
 2476                string.IsNullOrWhiteSpace(prefix))
 477            {
 1478                context.ReportDiagnostic(Diagnostic.Create(
 1479                    InvalidFilter,
 1480                    location,
 1481                    "CallbackDataPrefixAttribute must specify a non-empty callback data prefix."));
 482            }
 483        }
 484
 162485        foreach (AttributeData attribute in TelegramHandlerSymbols.GetAttributes(method.ContainingType, TelegramHandlerS
 79486                     .Concat(TelegramHandlerSymbols.GetAttributes(method, TelegramHandlerSymbols.MessageThreadIdAttribut
 487        {
 2488            if (attribute.ConstructorArguments.Length == 0 ||
 2489                attribute.ConstructorArguments[0].Values.IsDefaultOrEmpty ||
 4490                attribute.ConstructorArguments[0].Values.Any(static value => value.Value is not long threadId || threadI
 491            {
 1492                context.ReportDiagnostic(Diagnostic.Create(
 1493                    InvalidFilter,
 1494                    location,
 1495                    "MessageThreadIdAttribute must specify at least one positive Telegram message thread id."));
 496            }
 497        }
 79498    }
 499
 500    private static void AnalyzeChatTypeFilters(
 501        SymbolAnalysisContext context,
 502        IMethodSymbol method,
 503        string attributeMetadataName,
 504        string attributeDisplayName,
 505        Location? location)
 506    {
 330507        foreach (AttributeData attribute in TelegramHandlerSymbols.GetAttributes(
 158508                     method.ContainingType,
 158509                     attributeMetadataName,
 158510                     inherit: true)
 158511                 .Concat(TelegramHandlerSymbols.GetAttributes(
 158512                     method,
 158513                     attributeMetadataName,
 158514                     inherit: true)))
 515        {
 7516            if (attribute.ConstructorArguments.Length == 0 ||
 7517                attribute.ConstructorArguments[0].Values.IsDefaultOrEmpty ||
 7518                attribute.ConstructorArguments[0].Values.Any(
 12519                    static value => value.Value is not int chatType ||
 12520                                    !TelegramChatTypeFacts.IsKnown(chatType)))
 521            {
 6522                context.ReportDiagnostic(Diagnostic.Create(
 6523                    InvalidFilter,
 6524                    location,
 6525                    $"{attributeDisplayName} must specify at least one known Telegram chat type."));
 526            }
 527        }
 158528    }
 529
 530    private static void AnalyzeSenderIdFilters(
 531        SymbolAnalysisContext context,
 532        IMethodSymbol method,
 533        string attributeMetadataName,
 534        string attributeDisplayName,
 535        string senderDisplayName,
 536        Location? location)
 537    {
 328538        foreach (AttributeData attribute in TelegramHandlerSymbols.GetAttributes(
 158539                     method.ContainingType,
 158540                     attributeMetadataName,
 158541                     inherit: true)
 158542                 .Concat(TelegramHandlerSymbols.GetAttributes(
 158543                     method,
 158544                     attributeMetadataName,
 158545                     inherit: true)))
 546        {
 6547            if (attribute.ConstructorArguments.Length != 1 ||
 6548                attribute.ConstructorArguments[0].Values.IsDefault ||
 6549                attribute.ConstructorArguments[0].Values.Any(
 10550                    static value => value.Value is not long id || id <= 0))
 551            {
 2552                context.ReportDiagnostic(Diagnostic.Create(
 2553                    InvalidFilter,
 2554                    location,
 2555                    $"{attributeDisplayName} must contain only positive Telegram {senderDisplayName} ids."));
 556            }
 557        }
 158558    }
 559
 560    private static void AnalyzeCustomFilters(
 561        SymbolAnalysisContext context,
 562        IMethodSymbol method,
 563        HandlerKind routeKind,
 564        Location? location)
 565    {
 184566        foreach (AttributeData attribute in GetCustomFilterAttributes(method.ContainingType)
 86567                     .Concat(GetCustomFilterAttributes(method)))
 568        {
 6569            if (attribute.AttributeClass is not INamedTypeSymbol { TypeArguments.Length: 1 } attributeType)
 570            {
 0571                context.ReportDiagnostic(Diagnostic.Create(
 0572                    InvalidFilter,
 0573                    location,
 0574                    "UseFilterAttribute<TFilter> must specify a filter type."));
 0575                continue;
 576            }
 577
 6578            ITypeSymbol filterType = attributeType.TypeArguments[0];
 579
 6580            if (IsInvalidCustomFilterType(filterType))
 581            {
 1582                context.ReportDiagnostic(Diagnostic.Create(
 1583                    InvalidFilter,
 1584                    location,
 1585                    "Custom Telegram filter type must be a concrete closed type."));
 1586                continue;
 587            }
 588
 5589            if (!TryGetTelegramFilterContextTypes(filterType, out IReadOnlyList<string> contextTypes))
 590            {
 1591                context.ReportDiagnostic(Diagnostic.Create(
 1592                    InvalidFilter,
 1593                    location,
 1594                    "Custom Telegram filter type must implement ITelegramFilter<TContext>."));
 1595                continue;
 596            }
 597
 4598            if (!CustomFilterSupportsRouteKind(contextTypes, routeKind))
 599            {
 2600                context.ReportDiagnostic(Diagnostic.Create(
 2601                    InvalidFilter,
 2602                    location,
 2603                    "Custom Telegram filter context type is not compatible with the handler route kind."));
 604            }
 605        }
 606
 174607        foreach (AttributeData attribute in TelegramHandlerSymbols.GetTelegramFilterAttributes(method.ContainingType, in
 86608                     .Concat(TelegramHandlerSymbols.GetTelegramFilterAttributes(method, inherit: true)))
 609        {
 1610            if (attribute.AttributeClass is not { } attributeType ||
 1611                !TelegramHandlerSymbols.TryGetTelegramFilterAttributeFilterType(attribute, out ITypeSymbol filterType))
 612            {
 613                continue;
 614            }
 615
 1616            if (attributeType.IsGenericType)
 617            {
 0618                context.ReportDiagnostic(Diagnostic.Create(
 0619                    InvalidFilter,
 0620                    location,
 0621                    "Parameterized custom Telegram filter attribute types must be non-generic."));
 0622                continue;
 623            }
 624
 1625            if (IsInvalidCustomFilterType(filterType))
 626            {
 0627                context.ReportDiagnostic(Diagnostic.Create(
 0628                    InvalidFilter,
 0629                    location,
 0630                    "Custom Telegram filter type must be a concrete closed type."));
 0631                continue;
 632            }
 633
 1634            if (!TryGetParameterizedTelegramFilterContextTypes(filterType, attributeType, out IReadOnlyList<string> type
 635            {
 1636                context.ReportDiagnostic(Diagnostic.Create(
 1637                    InvalidFilter,
 1638                    location,
 1639                    "Parameterized custom Telegram filter attributes require a filter type that implements ITelegramFilt
 1640                continue;
 641            }
 642
 0643            if (!CustomFilterSupportsRouteKind(typedContextTypes, routeKind))
 644            {
 0645                context.ReportDiagnostic(Diagnostic.Create(
 0646                    InvalidFilter,
 0647                    location,
 0648                    "Custom Telegram filter context type is not compatible with the handler route kind."));
 649            }
 650        }
 86651    }
 652
 653    private static TelegramHandlerMetadataRouteKind ToMetadataRouteKind(HandlerKind routeKind)
 654    {
 86655        return routeKind switch
 86656        {
 0657            HandlerKind.Command => TelegramHandlerMetadataRouteKind.Command,
 62658            HandlerKind.Message => TelegramHandlerMetadataRouteKind.Message,
 14659            HandlerKind.Callback => TelegramHandlerMetadataRouteKind.Callback,
 10660            HandlerKind.ChatMember => TelegramHandlerMetadataRouteKind.ChatMember,
 0661            _ => TelegramHandlerMetadataRouteKind.Message
 86662        };
 663    }
 664
 665    private static string CanonicalizeChatUsername(string? username)
 666    {
 3667        if (username is null)
 668        {
 0669            return string.Empty;
 670        }
 671
 3672        string value = username.Trim();
 3673        return value.StartsWith("@", StringComparison.Ordinal) ? value.Substring(1) : value;
 674    }
 675
 676    private static IReadOnlyList<AttributeData> GetCustomFilterAttributes(ISymbol symbol)
 677    {
 172678        return TelegramHandlerSymbols.GetGenericAttributes(
 172679            symbol,
 172680            TelegramHandlerSymbols.GenericUseFilterAttribute,
 172681            inherit: true);
 682    }
 683
 684    private static bool IsInvalidCustomFilterType(ITypeSymbol type)
 685    {
 7686        if (type.TypeKind is TypeKind.Interface or TypeKind.TypeParameter)
 687        {
 0688            return true;
 689        }
 690
 7691        if (type is not INamedTypeSymbol namedType)
 692        {
 0693            return true;
 694        }
 695
 7696        return namedType.IsAbstract ||
 7697               namedType.IsUnboundGenericType ||
 7698               namedType.TypeArguments.Any(static argument => argument.TypeKind == TypeKind.TypeParameter);
 699    }
 700
 701    private static bool TryGetTelegramFilterContextTypes(
 702        ITypeSymbol filterType,
 703        out IReadOnlyList<string> contextTypes)
 704    {
 5705        string[] values = filterType.AllInterfaces
 5706            .Where(static candidate =>
 4707                string.Equals(
 4708                    candidate.OriginalDefinition.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat),
 4709                    TelegramHandlerSymbols.GenericTelegramFilter,
 4710                    StringComparison.Ordinal))
 4711            .Select(static candidate => candidate.TypeArguments[0].ToDisplayString(SymbolDisplayFormat.CSharpErrorMessag
 5712            .ToArray();
 713
 5714        contextTypes = values;
 5715        return values.Length > 0;
 716    }
 717
 718    private static bool TryGetParameterizedTelegramFilterContextTypes(
 719        ITypeSymbol filterType,
 720        ITypeSymbol attributeType,
 721        out IReadOnlyList<string> contextTypes)
 722    {
 1723        string[] values = filterType.AllInterfaces
 1724            .Where(candidate =>
 1725                string.Equals(
 1726                    candidate.OriginalDefinition.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat),
 1727                    TelegramHandlerSymbols.GenericParameterizedTelegramFilter,
 1728                    StringComparison.Ordinal) &&
 1729                candidate.TypeArguments.Length == 2 &&
 1730                SymbolEqualityComparer.Default.Equals(candidate.TypeArguments[1], attributeType))
 0731            .Select(static candidate => candidate.TypeArguments[0].ToDisplayString(SymbolDisplayFormat.CSharpErrorMessag
 1732            .ToArray();
 733
 1734        contextTypes = values;
 1735        return values.Length > 0;
 736    }
 737
 738    private static bool CustomFilterSupportsRouteKind(
 739        IReadOnlyList<string> contextTypes,
 740        HandlerKind routeKind)
 741    {
 4742        if (contextTypes.Contains(TelegramHandlerSymbols.TelegramUpdateContext, StringComparer.Ordinal))
 743        {
 1744            return true;
 745        }
 746
 3747        string expectedContext = routeKind switch
 3748        {
 1749            HandlerKind.Callback => TelegramHandlerSymbols.CallbackQueryContext,
 1750            HandlerKind.ChatMember => TelegramHandlerSymbols.ChatMemberUpdatedContext,
 1751            _ => TelegramHandlerSymbols.MessageContext
 3752        };
 753
 3754        return contextTypes.Contains(expectedContext, StringComparer.Ordinal);
 755    }
 756
 757    private static void AnalyzeChatMemberTransitions(
 758        SymbolAnalysisContext context,
 759        IMethodSymbol method,
 760        HandlerKind routeKind,
 761        Location? location)
 762    {
 86763        AttributeData[] transitionAttributes = TelegramHandlerSymbols.GetAttributes(method.ContainingType, TelegramHandl
 86764            .Concat(TelegramHandlerSymbols.GetAttributes(method, TelegramHandlerSymbols.ChatMemberTransitionAttribute, i
 86765            .ToArray();
 86766        AttributeData[] changedAttributes = TelegramHandlerSymbols.GetAttributes(method.ContainingType, TelegramHandlerS
 86767            .Concat(TelegramHandlerSymbols.GetAttributes(method, TelegramHandlerSymbols.ChatMemberChangedAttribute, inhe
 86768            .ToArray();
 769
 86770        if (transitionAttributes.Length == 0 && changedAttributes.Length == 0)
 771        {
 83772            return;
 773        }
 774
 3775        if (routeKind != HandlerKind.ChatMember)
 776        {
 1777            context.ReportDiagnostic(Diagnostic.Create(
 1778                InvalidFilter,
 1779                location,
 1780                "Chat member transition attributes can only be used on chat member update handlers."));
 1781            return;
 782        }
 783
 6784        foreach (AttributeData attribute in transitionAttributes)
 785        {
 1786            if (attribute.ConstructorArguments.Length == 0 ||
 1787                attribute.ConstructorArguments[0].Value is not int transition ||
 1788                !TelegramMemberStatusFacts.TryMapTransition(transition, out _, out _))
 789            {
 1790                context.ReportDiagnostic(Diagnostic.Create(
 1791                    InvalidFilter,
 1792                    location,
 1793                    "ChatMemberTransitionAttribute must specify a known Telegram member transition."));
 794            }
 795        }
 796
 6797        foreach (AttributeData attribute in changedAttributes)
 798        {
 1799            if (attribute.ConstructorArguments.Length < 2 ||
 1800                attribute.ConstructorArguments[0].Value is not int oldStatus ||
 1801                attribute.ConstructorArguments[1].Value is not int newStatus ||
 1802                !TelegramMemberStatusFacts.IsValid(oldStatus) ||
 1803                !TelegramMemberStatusFacts.IsValid(newStatus))
 804            {
 1805                context.ReportDiagnostic(Diagnostic.Create(
 1806                    InvalidFilter,
 1807                    location,
 1808                    "ChatMemberChangedAttribute must specify non-empty known Telegram member status sets."));
 809            }
 810        }
 2811    }
 812
 813    private static void AnalyzeTelegramRoleRequirements(
 814        SymbolAnalysisContext context,
 815        IMethodSymbol method,
 816        Location? location)
 817    {
 174818        foreach (AttributeData attribute in TelegramHandlerSymbols.GetAttributes(method.ContainingType, TelegramHandlerS
 86819                     .Concat(TelegramHandlerSymbols.GetAttributes(method, TelegramHandlerSymbols.RequireTelegramRoleAttr
 820        {
 1821            if (!TelegramMemberStatusFacts.TryGetRoleRequirementMask(attribute, out int allowedStatuses) ||
 1822                !TelegramMemberStatusFacts.IsValid(allowedStatuses))
 823            {
 1824                context.ReportDiagnostic(Diagnostic.Create(
 1825                    InvalidFilter,
 1826                    location,
 1827                    "RequireTelegramRoleAttribute must specify non-empty known Telegram member status sets."));
 828            }
 829        }
 86830    }
 831
 832    private static bool TryParseRouteTemplate(
 833        string? template,
 834        out IReadOnlyDictionary<string, RouteValueDefinition> values,
 835        out string reason)
 836    {
 12837        values = new Dictionary<string, RouteValueDefinition>(StringComparer.Ordinal);
 12838        reason = string.Empty;
 839
 12840        if (template is null ||
 12841            string.IsNullOrWhiteSpace(template))
 842        {
 0843            reason = "Route template must not be empty.";
 0844            return false;
 845        }
 846
 12847        Regex placeholderRegex = new Regex(
 12848            @"\{(?<name>[A-Za-z_][A-Za-z0-9_]*)(?:(?<nameOptional>\?)|:(?<constraint>[A-Za-z][A-Za-z0-9_]*)(?<constraint
 12849            RegexOptions.CultureInvariant);
 12850        Dictionary<string, RouteValueDefinition> parsedValues = new Dictionary<string, RouteValueDefinition>(StringCompa
 12851        int position = 0;
 852
 45853        foreach (Match match in placeholderRegex.Matches(template))
 854        {
 12855            if (match.Index != position &&
 12856                ContainsBrace(template.Substring(position, match.Index - position)))
 857            {
 0858                reason = $"Route template '{template}' contains an invalid placeholder.";
 0859                return false;
 860            }
 861
 12862            string name = match.Groups["name"].Value;
 12863            bool hasNameOptional = match.Groups["nameOptional"].Success;
 12864            bool hasConstraintOptional = match.Groups["constraintOptional"].Success;
 12865            string constraint = match.Groups["constraint"].Success
 12866                ? match.Groups["constraint"].Value
 12867                : "string";
 868
 12869            if (parsedValues.ContainsKey(name))
 870            {
 1871                reason = $"Route template '{template}' contains duplicate placeholder '{name}'.";
 1872                return false;
 873            }
 874
 11875            parsedValues.Add(name, new RouteValueDefinition(constraint, hasNameOptional || hasConstraintOptional));
 876
 11877            if (constraint is not ("string" or "int" or "long"))
 878            {
 2879                reason = $"Route template '{template}' uses unsupported placeholder constraint '{constraint}'.";
 2880                return false;
 881            }
 882
 9883            position = match.Index + match.Length;
 884        }
 885
 9886        if (ContainsBrace(template.Substring(position)))
 887        {
 1888            reason = $"Route template '{template}' contains an invalid placeholder.";
 1889            return false;
 890        }
 891
 8892        values = parsedValues;
 8893        return true;
 3894    }
 895
 896    private static bool TryParseRouteRegex(
 897        string? pattern,
 898        out IReadOnlyDictionary<string, RouteValueDefinition> values,
 899        out string reason)
 900    {
 3901        values = new Dictionary<string, RouteValueDefinition>(StringComparer.Ordinal);
 3902        reason = string.Empty;
 903
 3904        if (string.IsNullOrWhiteSpace(pattern))
 905        {
 0906            reason = "Route regex pattern must not be empty.";
 0907            return false;
 908        }
 909
 910        Regex regex;
 911
 912        try
 913        {
 3914            regex = new Regex(pattern, RegexOptions.CultureInvariant);
 2915        }
 1916        catch (ArgumentException exception)
 917        {
 1918            reason = $"Route regex pattern is invalid: {exception.Message}";
 1919            return false;
 920        }
 921
 2922        values = regex
 2923            .GetGroupNames()
 4924            .Where(static name => !int.TryParse(name, out _))
 2925            .ToDictionary(
 2926                static name => name,
 2927                static _ => new RouteValueDefinition(Constraint: null, IsOptional: false),
 2928                StringComparer.Ordinal);
 2929        return true;
 1930    }
 931
 932    private static string? GetConstructorString(AttributeData attribute)
 933    {
 21934        return attribute.ConstructorArguments.Length > 0 &&
 21935               attribute.ConstructorArguments[0].Value is string value
 21936            ? value.Trim()
 21937            : null;
 938    }
 939
 940    private static bool TryGetPrefixes(
 941        AttributeData attribute,
 942        out IReadOnlyList<string> prefixes,
 943        out string reason)
 944    {
 9945        prefixes = ["/"];
 9946        reason = string.Empty;
 947
 24948        foreach (KeyValuePair<string, TypedConstant> argument in attribute.NamedArguments)
 949        {
 5950            if (!string.Equals(argument.Key, "Prefixes", StringComparison.Ordinal))
 951            {
 952                continue;
 953            }
 954
 4955            if (argument.Value.Values.IsDefaultOrEmpty)
 956            {
 0957                reason = "Command route prefixes must contain at least one non-empty prefix.";
 0958                return false;
 959            }
 960
 4961            List<string> values = new List<string>();
 962
 14963            foreach (TypedConstant value in argument.Value.Values)
 964            {
 4965                if (value.Value is not string prefix ||
 4966                    string.IsNullOrWhiteSpace(prefix))
 967                {
 2968                    reason = "Command route prefixes must contain only non-empty prefix values.";
 2969                    return false;
 970                }
 971
 2972                values.Add(prefix.Trim());
 973            }
 974
 2975            prefixes = values.Distinct(StringComparer.Ordinal).ToArray();
 2976            return true;
 977        }
 978
 5979        return true;
 980    }
 981
 982    private static int GetCommandPrefixMode(AttributeData attribute)
 983    {
 17984        foreach (KeyValuePair<string, TypedConstant> argument in attribute.NamedArguments)
 985        {
 2986            if (string.Equals(argument.Key, "PrefixMode", StringComparison.Ordinal) &&
 2987                argument.Value.Value is int value)
 988            {
 1989                return value;
 990            }
 991        }
 992
 6993        return CommandPrefixModeRequired;
 994    }
 995
 996    private static bool TryValidateCommandPrefixMode(
 997        AttributeData attribute,
 998        int prefixMode,
 999        out string reason)
 1000    {
 71001        reason = string.Empty;
 1002
 71003        if (!IsSupportedCommandPrefixMode(prefixMode))
 1004        {
 01005            reason = "Command route prefix mode must be Required, Optional, or NoPrefix.";
 01006            return false;
 1007        }
 1008
 71009        if (prefixMode == CommandPrefixModeNoPrefix && HasNamedArgument(attribute, "Prefixes"))
 1010        {
 11011            reason = "Command route prefixes must not be configured when PrefixMode is NoPrefix.";
 11012            return false;
 1013        }
 1014
 61015        return true;
 1016    }
 1017
 1018    private static bool IsSupportedCommandPrefixMode(int value)
 1019    {
 71020        return value is CommandPrefixModeRequired or
 71021            CommandPrefixModeOptional or
 71022            CommandPrefixModeNoPrefix;
 1023    }
 1024
 1025    private static bool HasNamedArgument(
 1026        AttributeData attribute,
 1027        string name)
 1028    {
 31029        return attribute.NamedArguments.Any(argument => string.Equals(argument.Key, name, StringComparison.Ordinal));
 1030    }
 1031
 1032    private static bool CommandPatternStartsWithPrefix(
 1033        string pattern,
 1034        IReadOnlyList<string> prefixes)
 1035    {
 21036        string normalized = pattern.Trim();
 1037
 41038        return prefixes.Any(prefix => normalized.StartsWith(prefix, StringComparison.Ordinal));
 1039    }
 1040
 1041    private static bool GetNamedBool(
 1042        AttributeData attribute,
 1043        string name,
 1044        bool defaultValue)
 1045    {
 81046        foreach (KeyValuePair<string, TypedConstant> argument in attribute.NamedArguments)
 1047        {
 01048            if (string.Equals(argument.Key, name, StringComparison.Ordinal) &&
 01049                argument.Value.Value is bool value)
 1050            {
 01051                return value;
 1052            }
 1053        }
 1054
 41055        return defaultValue;
 1056    }
 1057
 1058    private static bool ContainsBrace(string value)
 1059    {
 211060        return value.IndexOf('{') >= 0 ||
 211061               value.IndexOf('}') >= 0;
 1062    }
 1063
 1064    private static bool IsSupportedRouteValueParameterType(ITypeSymbol type)
 1065    {
 41066        if (type.SpecialType is SpecialType.System_String or
 41067            SpecialType.System_Int32 or
 41068            SpecialType.System_Int64)
 1069        {
 21070            return true;
 1071        }
 1072
 21073        return type is INamedTypeSymbol
 21074        {
 21075            OriginalDefinition.SpecialType: SpecialType.System_Nullable_T,
 21076            TypeArguments.Length: 1
 21077        } nullable &&
 21078        nullable.TypeArguments[0].SpecialType is
 21079            SpecialType.System_Int32 or
 21080            SpecialType.System_Int64;
 1081    }
 1082
 1083    private static bool IsNullableRouteValueParameter(ITypeSymbol type)
 1084    {
 31085        if (type.SpecialType == SpecialType.System_String)
 1086        {
 11087            return type.NullableAnnotation == NullableAnnotation.Annotated;
 1088        }
 1089
 21090        return type is INamedTypeSymbol
 21091        {
 21092            OriginalDefinition.SpecialType: SpecialType.System_Nullable_T
 21093        };
 1094    }
 1095
 1096    private static SpecialType GetRouteValueParameterSpecialType(ITypeSymbol type)
 1097    {
 01098        if (type is INamedTypeSymbol
 01099            {
 01100                OriginalDefinition.SpecialType: SpecialType.System_Nullable_T,
 01101                TypeArguments.Length: 1
 01102            } nullable)
 1103        {
 01104            return nullable.TypeArguments[0].SpecialType;
 1105        }
 1106
 01107        return type.SpecialType;
 1108    }
 1109
 1110    private static bool RouteConstraintMatchesParameter(
 1111        string constraint,
 1112        ITypeSymbol parameterType)
 1113    {
 01114        SpecialType specialType = GetRouteValueParameterSpecialType(parameterType);
 1115
 01116        return constraint switch
 01117        {
 01118            "string" => specialType == SpecialType.System_String,
 01119            "int" => specialType == SpecialType.System_Int32,
 01120            "long" => specialType == SpecialType.System_Int64,
 01121            _ => false
 01122        };
 1123    }
 1124
 1125    private readonly record struct RouteValueDefinition(
 1126        string? Constraint,
 1127        bool IsOptional);
 1128
 1129    private static void ValidateCallbackRoute(
 1130        SymbolAnalysisContext context,
 1131        IMethodSymbol method,
 1132        ITypeSymbol? callbackPayloadType,
 1133        bool hasRawCallback,
 1134        Location? location)
 1135    {
 141136        if (callbackPayloadType is null)
 1137        {
 81138            if (hasRawCallback)
 1139            {
 271140                foreach (IParameterSymbol parameter in method.Parameters.Where(static parameter => HasCallbackDataAttrib
 1141                {
 11142                    context.ReportDiagnostic(Diagnostic.Create(
 11143                        InvalidTypedCallback,
 11144                        parameter.Locations.FirstOrDefault(static candidate => candidate.IsInSource) ?? location,
 11145                        $"Raw CallbackAttribute handler '{method.Name}' cannot bind typed callback payload parameter '{p
 1146                }
 1147            }
 1148
 81149            return;
 1150        }
 1151
 61152        if (IsInvalidCallbackPayloadType(callbackPayloadType))
 1153        {
 11154            context.ReportDiagnostic(Diagnostic.Create(
 11155                InvalidTypedCallback,
 11156                location,
 11157                $"CallbackAttribute<TPayload> on '{method.Name}' must use a concrete closed payload type."));
 1158        }
 1159
 61160        IParameterSymbol[] payloadParameters = method.Parameters
 121161            .Where(parameter => SymbolEqualityComparer.Default.Equals(parameter.Type, callbackPayloadType))
 61162            .ToArray();
 1163
 61164        if (payloadParameters.Length != 1)
 1165        {
 21166            context.ReportDiagnostic(Diagnostic.Create(
 21167                InvalidTypedCallback,
 21168                location,
 21169                $"CallbackAttribute<{callbackPayloadType.Name}> handler '{method.Name}' must declare exactly one {callba
 1170        }
 61171    }
 1172
 1173    private static bool HasCallbackDataAttribute(ITypeSymbol type)
 1174    {
 91175        return type is INamedTypeSymbol namedType &&
 91176               TelegramHandlerSymbols.HasAttribute(namedType, TelegramHandlerSymbols.CallbackDataAttribute);
 1177    }
 1178
 1179    private static bool IsInvalidCallbackPayloadType(ITypeSymbol type)
 1180    {
 61181        if (type.TypeKind is TypeKind.Interface or TypeKind.TypeParameter)
 1182        {
 11183            return true;
 1184        }
 1185
 51186        if (type is not INamedTypeSymbol namedType)
 1187        {
 01188            return true;
 1189        }
 1190
 51191        return namedType.IsAbstract ||
 51192               namedType.IsUnboundGenericType ||
 51193               namedType.TypeArguments.Any(static argument => argument.TypeKind == TypeKind.TypeParameter);
 1194    }
 1195
 1196    private static void ValidateContextParameter(
 1197        SymbolAnalysisContext context,
 1198        IMethodSymbol method,
 1199        HandlerKind kind,
 1200        string displayName,
 1201        Location? location)
 1202    {
 861203        string expectedContextType = kind switch
 861204        {
 141205            HandlerKind.Callback => TelegramHandlerSymbols.CallbackQueryContext,
 101206            HandlerKind.ChatMember => TelegramHandlerSymbols.ChatMemberUpdatedContext,
 621207            _ => TelegramHandlerSymbols.MessageContext
 861208        };
 861209        string expectedContextName = kind switch
 861210        {
 141211            HandlerKind.Callback => "CallbackQueryContext",
 101212            HandlerKind.ChatMember => "ChatMemberUpdatedContext",
 621213            _ => "MessageContext"
 861214        };
 861215        IParameterSymbol[] contextParameters = method.Parameters
 861216            .Where(static parameter =>
 1071217                TelegramHandlerSymbols.IsType(parameter.Type, TelegramHandlerSymbols.MessageContext) ||
 1071218                TelegramHandlerSymbols.IsType(parameter.Type, TelegramHandlerSymbols.CallbackQueryContext) ||
 1071219                TelegramHandlerSymbols.IsType(parameter.Type, TelegramHandlerSymbols.ChatMemberUpdatedContext) ||
 1071220                TelegramHandlerSymbols.IsType(parameter.Type, TelegramHandlerSymbols.TelegramUpdateContext))
 861221            .ToArray();
 1222
 861223        if (contextParameters.Length != 1 ||
 861224            !TelegramHandlerSymbols.IsType(contextParameters[0].Type, expectedContextType))
 1225        {
 21226            context.ReportDiagnostic(Diagnostic.Create(
 21227                InvalidContextParameter,
 21228                location,
 21229                GetDiagnosticHandlerKind(kind),
 21230                displayName,
 21231                expectedContextName));
 1232        }
 861233    }
 1234
 1235    private static string GetDiagnosticHandlerKind(HandlerKind kind)
 1236    {
 21237        return kind switch
 21238        {
 01239            HandlerKind.Callback => "callback",
 11240            HandlerKind.ChatMember => "chatmember",
 11241            _ => "message"
 21242        };
 1243    }
 1244
 1245    private static void ValidateCancellationTokens(
 1246        SymbolAnalysisContext context,
 1247        IMethodSymbol method,
 1248        string displayName,
 1249        Location? location)
 1250    {
 861251        if (method.Parameters.Count(static parameter =>
 1931252                TelegramHandlerSymbols.IsType(parameter.Type, TelegramHandlerSymbols.CancellationToken)) <= 1)
 1253        {
 851254            return;
 1255        }
 1256
 11257        context.ReportDiagnostic(Diagnostic.Create(
 11258            MultipleCancellationTokens,
 11259            location,
 11260            displayName));
 11261    }
 1262
 1263    private static bool IsSupportedReturnType(ITypeSymbol returnType)
 1264    {
 861265        return TelegramHandlerSymbols.IsType(returnType, TelegramHandlerSymbols.Task) ||
 861266               TelegramHandlerSymbols.IsType(returnType, TelegramHandlerSymbols.ValueTask);
 1267    }
 1268
 1269    private static bool IsValidCommand(string? command)
 1270    {
 61271        return command is not null &&
 61272               !string.IsNullOrWhiteSpace(command) &&
 61273               !command.StartsWith("/", StringComparison.Ordinal) &&
 61274               !command.Contains("@", StringComparison.Ordinal) &&
 61275               !command.Any(char.IsWhiteSpace);
 1276    }
 1277
 1278    private static bool HasAnyRouteAttribute(
 1279        IMethodSymbol method,
 1280        bool includeClassRouteAttributes)
 1281    {
 1461282        return TelegramHandlerSymbols.HasAnyRouteAttribute(method) ||
 1461283               (includeClassRouteAttributes &&
 1461284                TelegramHandlerSymbols.HasAnyRouteAttribute(method.ContainingType));
 1285    }
 1286
 1287    private static IReadOnlyList<AttributeData> GetRouteAttributes(
 1288        IMethodSymbol method,
 1289        string metadataName,
 1290        bool includeClassRouteAttributes)
 1291    {
 14581292        IReadOnlyList<AttributeData> methodAttributes = TelegramHandlerSymbols.GetAttributes(method, metadataName, inher
 1293
 14581294        if (!includeClassRouteAttributes)
 1295        {
 12651296            return methodAttributes;
 1297        }
 1298
 1931299        IReadOnlyList<AttributeData> typeAttributes = TelegramHandlerSymbols.GetAttributes(method.ContainingType, metada
 1300
 1931301        return typeAttributes.Count == 0
 1931302            ? methodAttributes
 1931303            : typeAttributes.Concat(methodAttributes).ToArray();
 1304    }
 1305
 1306    private static IReadOnlyList<AttributeData> GetGenericRouteAttributes(
 1307        IMethodSymbol method,
 1308        string metadataName,
 1309        bool includeClassRouteAttributes)
 1310    {
 971311        IReadOnlyList<AttributeData> methodAttributes = TelegramHandlerSymbols.GetGenericAttributes(method, metadataName
 1312
 971313        if (!includeClassRouteAttributes)
 1314        {
 821315            return methodAttributes;
 1316        }
 1317
 151318        IReadOnlyList<AttributeData> typeAttributes = TelegramHandlerSymbols.GetGenericAttributes(method.ContainingType,
 1319
 151320        return typeAttributes.Count == 0
 151321            ? methodAttributes
 151322            : typeAttributes.Concat(methodAttributes).ToArray();
 1323    }
 1324}

/_/src/TeleFlow.Generators/TelegramHandlerAnalyzer.Scenes.cs

#LineLine coverage
 1using Microsoft.CodeAnalysis;
 2using Microsoft.CodeAnalysis.CSharp.Syntax;
 3using Microsoft.CodeAnalysis.Diagnostics;
 4
 5namespace TeleFlow.Generators;
 6
 7public sealed partial class TelegramHandlerAnalyzer
 8{
 9    private static void AnalyzeSceneType(SymbolAnalysisContext context, INamedTypeSymbol type)
 10    {
 9911        AttributeData? sceneAttribute = TelegramHandlerSymbols.GetFirstAttribute(
 9912            type,
 9913            TelegramHandlerSymbols.SceneAttribute);
 14
 9915        if (sceneAttribute is null)
 16        {
 8817            return;
 18        }
 19
 2220        Location? location = type.Locations.FirstOrDefault(static candidate => candidate.IsInSource);
 1121        string? prefix = sceneAttribute.ConstructorArguments.Length > 0
 1122            ? sceneAttribute.ConstructorArguments[0].Value as string
 1123            : null;
 24
 1125        if (string.IsNullOrWhiteSpace(prefix))
 26        {
 127            context.ReportDiagnostic(Diagnostic.Create(
 128                InvalidScene,
 129                location,
 130                $"Scene '{type.Name}' must declare a non-empty prefix."));
 31        }
 32
 1133        if (type.TypeKind == TypeKind.Interface || type.IsAbstract || type.IsStatic)
 34        {
 035            context.ReportDiagnostic(Diagnostic.Create(
 036                InvalidScene,
 037                location,
 038                $"Scene '{type.Name}' must be a concrete non-static partial class."));
 39        }
 40
 4341        foreach (SyntaxReference syntaxReference in type.DeclaringSyntaxReferences)
 42        {
 1143            if (syntaxReference.GetSyntax() is not TypeDeclarationSyntax declaration ||
 4344                !declaration.Modifiers.Any(static modifier => modifier.Text == "partial"))
 45            {
 146                context.ReportDiagnostic(Diagnostic.Create(
 147                    InvalidScene,
 148                    location,
 149                    $"Scene '{type.Name}' must be declared as partial."));
 150                break;
 51            }
 52        }
 53
 1154        IPropertySymbol[] stateProperties = type
 1155            .GetMembers()
 1156            .OfType<IPropertySymbol>()
 1357            .Where(static property => property.DeclaredAccessibility == Accessibility.Public &&
 1358                                      property.IsStatic &&
 1359                                      TelegramHandlerSymbols.IsType(property.Type, TelegramHandlerSymbols.State))
 1160            .ToArray();
 61
 1162        if (stateProperties.Length == 0)
 63        {
 164            context.ReportDiagnostic(Diagnostic.Create(
 165                InvalidScene,
 166                location,
 167                $"Scene '{type.Name}' must declare at least one public static partial State property."));
 68        }
 69
 4470        foreach (IPropertySymbol property in stateProperties)
 71        {
 2272            Location? propertyLocation = property.Locations.FirstOrDefault(static candidate => candidate.IsInSource) ?? 
 1173            bool isPartial = property
 1174                .DeclaringSyntaxReferences
 1175                .Select(static reference => reference.GetSyntax())
 1176                .OfType<PropertyDeclarationSyntax>()
 5377                .Any(static declaration => declaration.Modifiers.Any(modifier => modifier.Text == "partial"));
 78
 1179            if (!isPartial)
 80            {
 281                context.ReportDiagnostic(Diagnostic.Create(
 282                    InvalidScene,
 283                    propertyLocation,
 284                    $"Scene state property '{type.Name}.{property.Name}' must be partial."));
 85            }
 86        }
 87
 1188        if (prefix is not null &&
 1189            !string.IsNullOrWhiteSpace(prefix))
 90        {
 1091            ReportDuplicateSceneStateIds(context, type, prefix, stateProperties, location);
 92        }
 1193    }
 94
 95    private static void AnalyzeSceneStep(
 96        SymbolAnalysisContext context,
 97        IMethodSymbol method,
 98        Location? location)
 99    {
 86100        AttributeData? sceneStep = TelegramHandlerSymbols.GetFirstAttribute(
 86101            method,
 86102            TelegramHandlerSymbols.SceneStepAttribute,
 86103            inherit: true);
 104
 86105        if (sceneStep is null)
 106        {
 80107            return;
 108        }
 109
 6110        if (!TelegramHandlerSymbols.HasAttribute(method.ContainingType, TelegramHandlerSymbols.SceneAttribute))
 111        {
 1112            context.ReportDiagnostic(Diagnostic.Create(
 1113                InvalidSceneStep,
 1114                location,
 1115                "SceneStepAttribute can only be used on handler methods declared inside a SceneAttribute type."));
 1116            return;
 117        }
 118
 5119        if (HasStateBindingAttributes(method.ContainingType) || HasStateBindingAttributes(method))
 120        {
 1121            context.ReportDiagnostic(Diagnostic.Create(
 1122                InvalidSceneStep,
 1123                location,
 1124                "SceneStepAttribute cannot be mixed with StateAttribute or StateAttribute<TStateGroup>."));
 1125            return;
 126        }
 127
 4128        string? stateName = sceneStep.ConstructorArguments.Length > 0
 4129            ? sceneStep.ConstructorArguments[0].Value as string
 4130            : null;
 131
 4132        if (stateName is null ||
 4133            string.IsNullOrWhiteSpace(stateName) ||
 4134            method.ContainingType.GetMembers(stateName).OfType<IPropertySymbol>().FirstOrDefault(IsUsableStateProperty) 
 135        {
 3136            context.ReportDiagnostic(Diagnostic.Create(
 3137                InvalidSceneStep,
 3138                location,
 3139                $"SceneStepAttribute references missing scene state '{method.ContainingType.Name}.{stateName}'."));
 140        }
 4141    }
 142
 143    private static bool HasStateBindingAttributes(ISymbol symbol)
 144    {
 10145        return TelegramHandlerSymbols.HasAttribute(
 10146                   symbol,
 10147                   TelegramHandlerSymbols.StateAttribute,
 10148                   inherit: true) ||
 10149               TelegramHandlerSymbols.HasGenericAttribute(
 10150                   symbol,
 10151                   TelegramHandlerSymbols.GenericStateAttribute,
 10152                   inherit: true);
 153    }
 154
 155    private static void ReportDuplicateSceneStateIds(
 156        SymbolAnalysisContext context,
 157        INamedTypeSymbol type,
 158        string prefix,
 159        IReadOnlyList<IPropertySymbol> stateProperties,
 160        Location? fallbackLocation)
 161    {
 10162        var duplicate = stateProperties
 10163            .Select(property => new
 10164            {
 10165                Property = property,
 10166                StateId = $"{prefix}:{GetSceneStateSegment(property)}"
 10167            })
 10168            .GroupBy(static item => item.StateId, StringComparer.Ordinal)
 19169            .FirstOrDefault(static group => group.Count() > 1);
 170
 10171        if (duplicate is null)
 172        {
 9173            return;
 174        }
 175
 1176        Location? location = duplicate
 2177            .Select(static item => item.Property.Locations.FirstOrDefault(static candidate => candidate.IsInSource))
 2178            .FirstOrDefault(static candidate => candidate is not null) ?? fallbackLocation;
 179
 1180        context.ReportDiagnostic(Diagnostic.Create(
 1181            InvalidScene,
 1182            location,
 1183            $"Scene '{type.Name}' has duplicate state id '{duplicate.Key}'."));
 1184    }
 185
 186    private static string GetSceneStateSegment(IPropertySymbol property)
 187    {
 10188        AttributeData? attribute = TelegramHandlerSymbols.GetFirstAttribute(
 10189            property,
 10190            TelegramHandlerSymbols.StateValueAttribute);
 191
 10192        if (attribute?.ConstructorArguments.Length > 0 &&
 10193            attribute.ConstructorArguments[0].Value is string value &&
 10194            !string.IsNullOrWhiteSpace(value))
 195        {
 2196            return value;
 197        }
 198
 8199        return ToSceneCamelCase(property.Name);
 200    }
 201
 202    private static string ToSceneCamelCase(string value)
 203    {
 8204        return value.Length == 0
 8205            ? value
 8206            : char.ToLowerInvariant(value[0]) + value.Substring(1);
 207    }
 208}

/_/src/TeleFlow.Generators/TelegramHandlerAnalyzer.State.cs

#LineLine coverage
 1using Microsoft.CodeAnalysis;
 2using Microsoft.CodeAnalysis.CSharp.Syntax;
 3using Microsoft.CodeAnalysis.Diagnostics;
 4
 5namespace TeleFlow.Generators;
 6
 7public sealed partial class TelegramHandlerAnalyzer
 8{
 9    private static void AnalyzeStateGroupType(SymbolAnalysisContext context, INamedTypeSymbol type)
 10    {
 9911        if (!TelegramHandlerSymbols.HasAttribute(type, TelegramHandlerSymbols.StateGroupAttribute))
 12        {
 9713            return;
 14        }
 15
 416        Location? location = type.Locations.FirstOrDefault(static candidate => candidate.IsInSource);
 17
 218        if (type.IsStatic)
 19        {
 020            context.ReportDiagnostic(Diagnostic.Create(
 021                InvalidStateGroup,
 022                location,
 023                $"State group '{type.Name}' must be a non-static partial class so it can be used as a generic StateAttri
 24        }
 25
 826        foreach (SyntaxReference syntaxReference in type.DeclaringSyntaxReferences)
 27        {
 228            if (syntaxReference.GetSyntax() is not Microsoft.CodeAnalysis.CSharp.Syntax.TypeDeclarationSyntax declaratio
 829                !declaration.Modifiers.Any(static modifier => modifier.Text == "partial"))
 30            {
 031                context.ReportDiagnostic(Diagnostic.Create(
 032                    InvalidStateGroup,
 033                    location,
 034                    $"State group '{type.Name}' must be declared as partial."));
 035                break;
 36            }
 37        }
 38
 239        IPropertySymbol[] stateProperties = type
 240            .GetMembers()
 241            .OfType<IPropertySymbol>()
 242            .Where(static property => property.DeclaredAccessibility == Accessibility.Public &&
 243                                      property.IsStatic &&
 244                                      TelegramHandlerSymbols.IsType(property.Type, TelegramHandlerSymbols.State))
 245            .ToArray();
 46
 247        if (stateProperties.Length == 0)
 48        {
 049            context.ReportDiagnostic(Diagnostic.Create(
 050                InvalidStateGroup,
 051                location,
 052                $"State group '{type.Name}' must declare at least one public static partial State property."));
 53        }
 54
 855        foreach (IPropertySymbol property in stateProperties)
 56        {
 457            Location? propertyLocation = property.Locations.FirstOrDefault(static candidate => candidate.IsInSource) ?? 
 258            bool isPartial = property
 259                .DeclaringSyntaxReferences
 260                .Select(static reference => reference.GetSyntax())
 261                .OfType<PropertyDeclarationSyntax>()
 1062                .Any(static declaration => declaration.Modifiers.Any(modifier => modifier.Text == "partial"));
 63
 264            if (!isPartial)
 65            {
 066                context.ReportDiagnostic(Diagnostic.Create(
 067                    InvalidStateGroup,
 068                    propertyLocation,
 069                    $"State group property '{type.Name}.{property.Name}' must be partial."));
 70            }
 71        }
 272    }
 73
 74    private static void ValidateTypedStateAttributes(SymbolAnalysisContext context, ISymbol symbol)
 75    {
 34676        foreach (AttributeData attribute in TelegramHandlerSymbols.GetGenericAttributes(
 17277                     symbol,
 17278                     TelegramHandlerSymbols.GenericStateAttribute,
 17279                     inherit: true))
 80        {
 281            Location? location = symbol.Locations.FirstOrDefault(static candidate => candidate.IsInSource);
 82
 183            if (attribute.AttributeClass is not INamedTypeSymbol { TypeArguments.Length: 1 } attributeType ||
 184                attributeType.TypeArguments[0] is not INamedTypeSymbol groupType ||
 185                !HasStateContainerAttribute(groupType))
 86            {
 087                context.ReportDiagnostic(Diagnostic.Create(
 088                    InvalidTypedState,
 089                    location,
 090                    "Typed StateAttribute must reference a type marked with StateGroupAttribute or SceneAttribute."));
 091                continue;
 92            }
 93
 194            string? stateName = attribute.ConstructorArguments.Length > 0
 195                ? attribute.ConstructorArguments[0].Value as string
 196                : null;
 97
 198            if (stateName is null ||
 199                string.IsNullOrWhiteSpace(stateName) ||
 1100                groupType.GetMembers(stateName).OfType<IPropertySymbol>().FirstOrDefault(IsUsableStateProperty) is null)
 101            {
 1102                context.ReportDiagnostic(Diagnostic.Create(
 1103                    InvalidTypedState,
 1104                    location,
 1105                    $"Typed StateAttribute references missing state '{groupType.Name}.{stateName}'."));
 106            }
 107        }
 172108    }
 109
 110    private static bool HasStateContainerAttribute(INamedTypeSymbol type)
 111    {
 1112        return TelegramHandlerSymbols.HasAttribute(type, TelegramHandlerSymbols.StateGroupAttribute) ||
 1113               TelegramHandlerSymbols.HasAttribute(type, TelegramHandlerSymbols.SceneAttribute);
 114    }
 115
 116    private static bool IsUsableStateProperty(IPropertySymbol property)
 117    {
 3118        return property.DeclaredAccessibility == Accessibility.Public &&
 3119               property.IsStatic &&
 3120               TelegramHandlerSymbols.IsType(property.Type, TelegramHandlerSymbols.State);
 121    }
 122}

Methods/Properties

AnalyzeCallbackDataType(Microsoft.CodeAnalysis.Diagnostics.SymbolAnalysisContext,Microsoft.CodeAnalysis.INamedTypeSymbol,System.Collections.Concurrent.ConcurrentBag`1<TeleFlow.Generators.TelegramHandlerAnalyzer/CallbackPrefixRegistration>)
GetCallbackDataFields()
AnalyzeClassLevelRouteUsage(Microsoft.CodeAnalysis.Diagnostics.SymbolAnalysisContext,Microsoft.CodeAnalysis.INamedTypeSymbol)
AnalyzeClassBasedHandlerType(Microsoft.CodeAnalysis.Diagnostics.SymbolAnalysisContext,Microsoft.CodeAnalysis.INamedTypeSymbol,System.Collections.Concurrent.ConcurrentBag`1<TeleFlow.Generators.TelegramHandlerAnalyzer/CommandRegistration>)
GetDeclaredRouteMethodsExceptHandleAsync(Microsoft.CodeAnalysis.INamedTypeSymbol)
GetDeclaredHandleAsyncMethods(Microsoft.CodeAnalysis.INamedTypeSymbol)
IsClassBasedHandlerType(Microsoft.CodeAnalysis.INamedTypeSymbol)
IsClassBasedRouteCompatible(Microsoft.CodeAnalysis.INamedTypeSymbol,TeleFlow.Generators.TelegramHandlerAnalyzer/HandlerKind,Microsoft.CodeAnalysis.ITypeSymbol,System.String&)
TryGetCallbackHandlerPayloadType(Microsoft.CodeAnalysis.INamedTypeSymbol,Microsoft.CodeAnalysis.ITypeSymbol&)
IsAssignableTo(Microsoft.CodeAnalysis.INamedTypeSymbol,System.String)
Initialize(Microsoft.CodeAnalysis.Diagnostics.AnalysisContext)
AnalyzeNamedType(Microsoft.CodeAnalysis.Diagnostics.SymbolAnalysisContext,System.Collections.Concurrent.ConcurrentBag`1<TeleFlow.Generators.TelegramHandlerAnalyzer/CommandRegistration>,System.Collections.Concurrent.ConcurrentBag`1<TeleFlow.Generators.TelegramHandlerAnalyzer/CallbackPrefixRegistration>)
AnalyzeMethod(Microsoft.CodeAnalysis.Diagnostics.SymbolAnalysisContext,System.Collections.Concurrent.ConcurrentBag`1<TeleFlow.Generators.TelegramHandlerAnalyzer/CommandRegistration>)
AnalyzeHandlerMethod(Microsoft.CodeAnalysis.Diagnostics.SymbolAnalysisContext,Microsoft.CodeAnalysis.IMethodSymbol,System.Collections.Concurrent.ConcurrentBag`1<TeleFlow.Generators.TelegramHandlerAnalyzer/CommandRegistration>,System.Boolean)
AnalyzeAutoAnswerCallback(Microsoft.CodeAnalysis.Diagnostics.SymbolAnalysisContext,Microsoft.CodeAnalysis.IMethodSymbol,System.Nullable`1<TeleFlow.Generators.TelegramHandlerAnalyzer/HandlerKind>)
.cctor()
.ctor()
CreateDescriptor(System.String,System.String,System.String,System.String)
AnalyzeErrorHandlerMethod(Microsoft.CodeAnalysis.Diagnostics.SymbolAnalysisContext,Microsoft.CodeAnalysis.IMethodSymbol)
GetErrorHandlerExceptionTypes(Microsoft.CodeAnalysis.Diagnostics.SymbolAnalysisContext,System.Collections.Generic.IReadOnlyList`1<Microsoft.CodeAnalysis.AttributeData>,Microsoft.CodeAnalysis.Location)
ValidateErrorHandlerParameters(Microsoft.CodeAnalysis.Diagnostics.SymbolAnalysisContext,Microsoft.CodeAnalysis.IMethodSymbol,System.Collections.Generic.IReadOnlyList`1<Microsoft.CodeAnalysis.ITypeSymbol>,Microsoft.CodeAnalysis.Location)
IsSupportedErrorHandlerReturnType(Microsoft.CodeAnalysis.ITypeSymbol)
IsExceptionType(Microsoft.CodeAnalysis.ITypeSymbol)
IsTelegramContextType(Microsoft.CodeAnalysis.ITypeSymbol)
IsAssignableFrom(Microsoft.CodeAnalysis.ITypeSymbol,Microsoft.CodeAnalysis.ITypeSymbol)
ReportInvalidErrorHandler(Microsoft.CodeAnalysis.Diagnostics.SymbolAnalysisContext,Microsoft.CodeAnalysis.Location,System.String)
AnalyzeInheritedHandlerMethods(Microsoft.CodeAnalysis.Diagnostics.SymbolAnalysisContext,Microsoft.CodeAnalysis.INamedTypeSymbol)
GetInheritedHandlerMethods()
IsConcreteHandlerCandidate(Microsoft.CodeAnalysis.INamedTypeSymbol)
IsOverriddenBy(Microsoft.CodeAnalysis.INamedTypeSymbol,Microsoft.CodeAnalysis.IMethodSymbol)
AnalyzeTelegramModuleType(Microsoft.CodeAnalysis.Diagnostics.SymbolAnalysisContext,Microsoft.CodeAnalysis.INamedTypeSymbol)
HasTelegramHandlerMethods(Microsoft.CodeAnalysis.INamedTypeSymbol)
.cctor()
AnalyzeMissingRouteAttribute(Microsoft.CodeAnalysis.Diagnostics.SymbolAnalysisContext,Microsoft.CodeAnalysis.IMethodSymbol)
TryGetRouteLessHandlerAttribute(Microsoft.CodeAnalysis.IMethodSymbol,Microsoft.CodeAnalysis.AttributeData&)
TryGetFirstKnownAttribute(Microsoft.CodeAnalysis.IMethodSymbol,System.Collections.Generic.IReadOnlyList`1<System.String>,Microsoft.CodeAnalysis.AttributeData&)
TryGetFirstKnownGenericAttribute(Microsoft.CodeAnalysis.IMethodSymbol,System.Collections.Generic.IReadOnlyList`1<System.String>,Microsoft.CodeAnalysis.AttributeData&)
TryGetFirst(System.Collections.Generic.IEnumerable`1<Microsoft.CodeAnalysis.AttributeData>,Microsoft.CodeAnalysis.AttributeData&)
GetRouteKind(Microsoft.CodeAnalysis.IMethodSymbol,System.Boolean,Microsoft.CodeAnalysis.ITypeSymbol&,System.Boolean&,System.Boolean&)
GetMessageRouteAttributeCount(Microsoft.CodeAnalysis.IMethodSymbol,System.Boolean)
HasAnyMessageRouteAttribute(Microsoft.CodeAnalysis.IMethodSymbol,System.Boolean)
GetChatMemberRouteAttributeCount(Microsoft.CodeAnalysis.IMethodSymbol,System.Boolean)
AnalyzeCommandRoutes(Microsoft.CodeAnalysis.Diagnostics.SymbolAnalysisContext,Microsoft.CodeAnalysis.IMethodSymbol,System.Collections.Concurrent.ConcurrentBag`1<TeleFlow.Generators.TelegramHandlerAnalyzer/CommandRegistration>,System.String,Microsoft.CodeAnalysis.Location,System.Boolean)
AnalyzeTemplateAndRegexRoutes(Microsoft.CodeAnalysis.Diagnostics.SymbolAnalysisContext,Microsoft.CodeAnalysis.IMethodSymbol,TeleFlow.Generators.TelegramHandlerAnalyzer/HandlerKind,Microsoft.CodeAnalysis.Location,System.Boolean)
ValidateRouteValueParameters(Microsoft.CodeAnalysis.Diagnostics.SymbolAnalysisContext,Microsoft.CodeAnalysis.IMethodSymbol,System.Collections.Generic.IReadOnlyList`1<System.Collections.Generic.IReadOnlyDictionary`2<System.String,TeleFlow.Generators.TelegramHandlerAnalyzer/RouteValueDefinition>>,Microsoft.CodeAnalysis.Location,System.Boolean)
AnalyzeBuiltInFilters(Microsoft.CodeAnalysis.Diagnostics.SymbolAnalysisContext,Microsoft.CodeAnalysis.IMethodSymbol,TeleFlow.Generators.TelegramHandlerAnalyzer/HandlerKind,Microsoft.CodeAnalysis.Location)
AnalyzeChatTypeFilters(Microsoft.CodeAnalysis.Diagnostics.SymbolAnalysisContext,Microsoft.CodeAnalysis.IMethodSymbol,System.String,System.String,Microsoft.CodeAnalysis.Location)
AnalyzeSenderIdFilters(Microsoft.CodeAnalysis.Diagnostics.SymbolAnalysisContext,Microsoft.CodeAnalysis.IMethodSymbol,System.String,System.String,System.String,Microsoft.CodeAnalysis.Location)
AnalyzeCustomFilters(Microsoft.CodeAnalysis.Diagnostics.SymbolAnalysisContext,Microsoft.CodeAnalysis.IMethodSymbol,TeleFlow.Generators.TelegramHandlerAnalyzer/HandlerKind,Microsoft.CodeAnalysis.Location)
ToMetadataRouteKind(TeleFlow.Generators.TelegramHandlerAnalyzer/HandlerKind)
CanonicalizeChatUsername(System.String)
GetCustomFilterAttributes(Microsoft.CodeAnalysis.ISymbol)
IsInvalidCustomFilterType(Microsoft.CodeAnalysis.ITypeSymbol)
TryGetTelegramFilterContextTypes(Microsoft.CodeAnalysis.ITypeSymbol,System.Collections.Generic.IReadOnlyList`1<System.String>&)
TryGetParameterizedTelegramFilterContextTypes(Microsoft.CodeAnalysis.ITypeSymbol,Microsoft.CodeAnalysis.ITypeSymbol,System.Collections.Generic.IReadOnlyList`1<System.String>&)
CustomFilterSupportsRouteKind(System.Collections.Generic.IReadOnlyList`1<System.String>,TeleFlow.Generators.TelegramHandlerAnalyzer/HandlerKind)
AnalyzeChatMemberTransitions(Microsoft.CodeAnalysis.Diagnostics.SymbolAnalysisContext,Microsoft.CodeAnalysis.IMethodSymbol,TeleFlow.Generators.TelegramHandlerAnalyzer/HandlerKind,Microsoft.CodeAnalysis.Location)
AnalyzeTelegramRoleRequirements(Microsoft.CodeAnalysis.Diagnostics.SymbolAnalysisContext,Microsoft.CodeAnalysis.IMethodSymbol,Microsoft.CodeAnalysis.Location)
TryParseRouteTemplate(System.String,System.Collections.Generic.IReadOnlyDictionary`2<System.String,TeleFlow.Generators.TelegramHandlerAnalyzer/RouteValueDefinition>&,System.String&)
TryParseRouteRegex(System.String,System.Collections.Generic.IReadOnlyDictionary`2<System.String,TeleFlow.Generators.TelegramHandlerAnalyzer/RouteValueDefinition>&,System.String&)
GetConstructorString(Microsoft.CodeAnalysis.AttributeData)
TryGetPrefixes(Microsoft.CodeAnalysis.AttributeData,System.Collections.Generic.IReadOnlyList`1<System.String>&,System.String&)
GetCommandPrefixMode(Microsoft.CodeAnalysis.AttributeData)
TryValidateCommandPrefixMode(Microsoft.CodeAnalysis.AttributeData,System.Int32,System.String&)
IsSupportedCommandPrefixMode(System.Int32)
HasNamedArgument(Microsoft.CodeAnalysis.AttributeData,System.String)
CommandPatternStartsWithPrefix(System.String,System.Collections.Generic.IReadOnlyList`1<System.String>)
GetNamedBool(Microsoft.CodeAnalysis.AttributeData,System.String,System.Boolean)
ContainsBrace(System.String)
IsSupportedRouteValueParameterType(Microsoft.CodeAnalysis.ITypeSymbol)
IsNullableRouteValueParameter(Microsoft.CodeAnalysis.ITypeSymbol)
GetRouteValueParameterSpecialType(Microsoft.CodeAnalysis.ITypeSymbol)
RouteConstraintMatchesParameter(System.String,Microsoft.CodeAnalysis.ITypeSymbol)
ValidateCallbackRoute(Microsoft.CodeAnalysis.Diagnostics.SymbolAnalysisContext,Microsoft.CodeAnalysis.IMethodSymbol,Microsoft.CodeAnalysis.ITypeSymbol,System.Boolean,Microsoft.CodeAnalysis.Location)
HasCallbackDataAttribute(Microsoft.CodeAnalysis.ITypeSymbol)
IsInvalidCallbackPayloadType(Microsoft.CodeAnalysis.ITypeSymbol)
ValidateContextParameter(Microsoft.CodeAnalysis.Diagnostics.SymbolAnalysisContext,Microsoft.CodeAnalysis.IMethodSymbol,TeleFlow.Generators.TelegramHandlerAnalyzer/HandlerKind,System.String,Microsoft.CodeAnalysis.Location)
GetDiagnosticHandlerKind(TeleFlow.Generators.TelegramHandlerAnalyzer/HandlerKind)
ValidateCancellationTokens(Microsoft.CodeAnalysis.Diagnostics.SymbolAnalysisContext,Microsoft.CodeAnalysis.IMethodSymbol,System.String,Microsoft.CodeAnalysis.Location)
IsSupportedReturnType(Microsoft.CodeAnalysis.ITypeSymbol)
IsValidCommand(System.String)
HasAnyRouteAttribute(Microsoft.CodeAnalysis.IMethodSymbol,System.Boolean)
GetRouteAttributes(Microsoft.CodeAnalysis.IMethodSymbol,System.String,System.Boolean)
GetGenericRouteAttributes(Microsoft.CodeAnalysis.IMethodSymbol,System.String,System.Boolean)
AnalyzeSceneType(Microsoft.CodeAnalysis.Diagnostics.SymbolAnalysisContext,Microsoft.CodeAnalysis.INamedTypeSymbol)
AnalyzeSceneStep(Microsoft.CodeAnalysis.Diagnostics.SymbolAnalysisContext,Microsoft.CodeAnalysis.IMethodSymbol,Microsoft.CodeAnalysis.Location)
HasStateBindingAttributes(Microsoft.CodeAnalysis.ISymbol)
ReportDuplicateSceneStateIds(Microsoft.CodeAnalysis.Diagnostics.SymbolAnalysisContext,Microsoft.CodeAnalysis.INamedTypeSymbol,System.String,System.Collections.Generic.IReadOnlyList`1<Microsoft.CodeAnalysis.IPropertySymbol>,Microsoft.CodeAnalysis.Location)
GetSceneStateSegment(Microsoft.CodeAnalysis.IPropertySymbol)
ToSceneCamelCase(System.String)
AnalyzeStateGroupType(Microsoft.CodeAnalysis.Diagnostics.SymbolAnalysisContext,Microsoft.CodeAnalysis.INamedTypeSymbol)
ValidateTypedStateAttributes(Microsoft.CodeAnalysis.Diagnostics.SymbolAnalysisContext,Microsoft.CodeAnalysis.ISymbol)
HasStateContainerAttribute(Microsoft.CodeAnalysis.INamedTypeSymbol)
IsUsableStateProperty(Microsoft.CodeAnalysis.IPropertySymbol)