forked from Azure/azure-sdk-tools
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathEnforceToolsExceptionHandling.cs
More file actions
83 lines (74 loc) · 3.14 KB
/
EnforceToolsExceptionHandling.cs
File metadata and controls
83 lines (74 loc) · 3.14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
using System;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
namespace Azure.Sdk.Tools.Cli.Analyzer
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class EnforceToolsExceptionHandlingAnalyzer : DiagnosticAnalyzer
{
public const string Id = "MCP001";
private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(
Id,
"McpServerTool methods must wrap body in try/catch, see the README within the tools directory for examples",
"Method '{0}' must have its entire body inside 'try {} catch(Exception) {}'",
"Reliability",
DiagnosticSeverity.Error,
isEnabledByDefault: true);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
=> ImmutableArray.Create(Rule);
public override void Initialize(AnalysisContext ctx)
{
ctx.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
ctx.EnableConcurrentExecution();
ctx.RegisterSyntaxNodeAction(AnalyzeMethod, SyntaxKind.MethodDeclaration);
}
/// <summary>
/// This is the main bulk of this analyzer. It scans for methods that are decorated with MCPServerTool attribute,
/// and ensures those checks properly wrap their body in a try/catch block. This catch should be a generalized System.Exception.
/// Within the catch block, there must be a SetFailure() call.
/// </summary>
/// <param name="ctx"></param>
private static void AnalyzeMethod(SyntaxNodeAnalysisContext ctx)
{
var md = (MethodDeclarationSyntax)ctx.Node;
// confirm that the method is marked with the correct attribute
bool hasAttr = md.AttributeLists
.SelectMany(a => a.Attributes)
.Any(a => a.Name.ToString().Contains("McpServerTool"));
// if it doesn't, just return
if (!hasAttr)
{
return;
}
var body = md.Body;
if (body == null)
{
return;
}
// check that try statements surround the entire body
var stmts = body.Statements;
foreach (var stmt in stmts)
{
if (stmt is LocalDeclarationStatementSyntax)
{
continue;
}
if (!(stmt is TryStatementSyntax tryStmt))
{
ctx.ReportDiagnostic(Diagnostic.Create(Rule, md.Identifier.GetLocation(), md.Identifier.Text));
continue;
}
// verify there’s a catch(Exception)
bool hasExCatch = tryStmt.Catches.Any(c => c.Declaration?.Type.ToString() == "Exception");
if (!hasExCatch)
{
ctx.ReportDiagnostic(Diagnostic.Create(Rule, md.Identifier.GetLocation(), md.Identifier.Text));
}
}
}
}
}