| | | 1 | | using System.Collections.Immutable; |
| | | 2 | | |
| | | 3 | | using Microsoft.CodeAnalysis; |
| | | 4 | | using Microsoft.CodeAnalysis.Diagnostics; |
| | | 5 | | |
| | | 6 | | namespace NexusLabs.Needlr.AgentFramework.Analyzers; |
| | | 7 | | |
| | | 8 | | /// <summary> |
| | | 9 | | /// Analyzer that detects <c>[AgentFunction]</c> methods and their parameters that are |
| | | 10 | | /// missing <c>[System.ComponentModel.Description]</c> attributes. |
| | | 11 | | /// </summary> |
| | | 12 | | /// <remarks> |
| | | 13 | | /// <b>NDLRMAF012</b> (Warning): An <c>[AgentFunction]</c> method has no <c>[Description]</c>.<br/> |
| | | 14 | | /// <b>NDLRMAF013</b> (Warning): A non-<c>CancellationToken</c> parameter of an <c>[AgentFunction]</c> |
| | | 15 | | /// method has no <c>[Description]</c>. |
| | | 16 | | /// </remarks> |
| | | 17 | | [DiagnosticAnalyzer(LanguageNames.CSharp)] |
| | | 18 | | public sealed class AgentFunctionDescriptionAnalyzer : DiagnosticAnalyzer |
| | | 19 | | { |
| | | 20 | | private const string AgentFunctionAttributeName = "NexusLabs.Needlr.AgentFramework.AgentFunctionAttribute"; |
| | | 21 | | private const string DescriptionAttributeName = "System.ComponentModel.DescriptionAttribute"; |
| | | 22 | | private const string CancellationTokenTypeName = "System.Threading.CancellationToken"; |
| | | 23 | | |
| | | 24 | | public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => |
| | 312 | 25 | | ImmutableArray.Create( |
| | 312 | 26 | | MafDiagnosticDescriptors.AgentFunctionMissingDescription, |
| | 312 | 27 | | MafDiagnosticDescriptors.AgentFunctionParameterMissingDescription); |
| | | 28 | | |
| | | 29 | | public override void Initialize(AnalysisContext context) |
| | | 30 | | { |
| | 23 | 31 | | context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); |
| | 23 | 32 | | context.EnableConcurrentExecution(); |
| | 23 | 33 | | context.RegisterSymbolAction(AnalyzeMethod, SymbolKind.Method); |
| | 23 | 34 | | } |
| | | 35 | | |
| | | 36 | | private static void AnalyzeMethod(SymbolAnalysisContext context) |
| | | 37 | | { |
| | 366 | 38 | | var method = (IMethodSymbol)context.Symbol; |
| | | 39 | | |
| | 366 | 40 | | var agentFunctionAttr = method.GetAttributes() |
| | 381 | 41 | | .FirstOrDefault(a => a.AttributeClass?.ToDisplayString() == AgentFunctionAttributeName); |
| | | 42 | | |
| | 366 | 43 | | if (agentFunctionAttr is null) |
| | 351 | 44 | | return; |
| | | 45 | | |
| | 15 | 46 | | var attrLocation = agentFunctionAttr.ApplicationSyntaxReference?.SyntaxTree is { } tree |
| | 15 | 47 | | ? Location.Create(tree, agentFunctionAttr.ApplicationSyntaxReference.Span) |
| | 15 | 48 | | : method.Locations[0]; |
| | | 49 | | |
| | 15 | 50 | | var hasDescription = method.GetAttributes() |
| | 37 | 51 | | .Any(a => a.AttributeClass?.ToDisplayString() == DescriptionAttributeName); |
| | | 52 | | |
| | 15 | 53 | | if (!hasDescription) |
| | | 54 | | { |
| | 8 | 55 | | context.ReportDiagnostic(Diagnostic.Create( |
| | 8 | 56 | | MafDiagnosticDescriptors.AgentFunctionMissingDescription, |
| | 8 | 57 | | attrLocation, |
| | 8 | 58 | | method.Name)); |
| | | 59 | | } |
| | | 60 | | |
| | 60 | 61 | | foreach (var parameter in method.Parameters) |
| | | 62 | | { |
| | 15 | 63 | | if (parameter.Type.ToDisplayString() == CancellationTokenTypeName) |
| | | 64 | | continue; |
| | | 65 | | |
| | 14 | 66 | | var paramHasDescription = parameter.GetAttributes() |
| | 18 | 67 | | .Any(a => a.AttributeClass?.ToDisplayString() == DescriptionAttributeName); |
| | | 68 | | |
| | 14 | 69 | | if (!paramHasDescription) |
| | | 70 | | { |
| | 10 | 71 | | var paramLocation = parameter.Locations.FirstOrDefault() ?? method.Locations[0]; |
| | | 72 | | |
| | 10 | 73 | | context.ReportDiagnostic(Diagnostic.Create( |
| | 10 | 74 | | MafDiagnosticDescriptors.AgentFunctionParameterMissingDescription, |
| | 10 | 75 | | paramLocation, |
| | 10 | 76 | | parameter.Name, |
| | 10 | 77 | | method.Name)); |
| | | 78 | | } |
| | | 79 | | } |
| | 15 | 80 | | } |
| | | 81 | | } |