-
Notifications
You must be signed in to change notification settings - Fork 689
Expand file tree
/
Copy pathAvoidRespWriteUtilsAnalyzer.cs
More file actions
151 lines (126 loc) · 6.07 KB
/
Copy pathAvoidRespWriteUtilsAnalyzer.cs
File metadata and controls
151 lines (126 loc) · 6.07 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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
namespace Garnet.analyzers
{
/// <summary>
/// Analyzer which flags uses of RespWriteUtils outside of the RespServerSessionOutput helper methods.
///
/// The intent is to force consistency in output for handling large outputs, error tracking, constant use, etc.
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class AvoidRespWriteUtilsAnalyzer : DiagnosticAnalyzer
{
private static DiagnosticDescriptor AvoidRespWriteUtils { get; } = new("GARNET0001", "Avoid direct use of RespWriteUtils", "If possible use RespServerSession.{0} instead of RespWriteUtils.{1}", "Correctness", DiagnosticSeverity.Warning, isEnabledByDefault: true);
/// <inheritdoc/>
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = [AvoidRespWriteUtils];
/// <inheritdoc/>
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.ReportDiagnostics | GeneratedCodeAnalysisFlags.Analyze);
context.EnableConcurrentExecution();
context.RegisterCompilationStartAction(
static compilationStartContext =>
{
var respWriteUtilsType = compilationStartContext.Compilation.GetTypeByMetadataName("Garnet.common.RespWriteUtils");
var respServerSessionType = compilationStartContext.Compilation.GetTypeByMetadataName("Garnet.server.RespServerSession");
if (respWriteUtilsType is not null && respServerSessionType is not null)
{
var suggestionLookup = BuildLookup(respWriteUtilsType, respServerSessionType);
compilationStartContext.RegisterSyntaxNodeAction(syntaxNodeContext => AnalyzeCaller(syntaxNodeContext, respWriteUtilsType, suggestionLookup), SyntaxKind.InvocationExpression);
}
}
);
// Build a map of TryWriteXXX methods on RespWriteUtils -> WriteXXX methods on RespServerSession
static Dictionary<string, string> BuildLookup(INamedTypeSymbol respWriteUtilsType, INamedTypeSymbol respServerSessionType)
{
var tryWriteMethods = new HashSet<string>();
foreach (var member in respWriteUtilsType.GetMembers())
{
if (member is not IMethodSymbol mtdSymbol)
{
continue;
}
if (!member.Name.StartsWith("TryWrite"))
{
continue;
}
_ = tryWriteMethods.Add(member.Name);
}
var lookup = new Dictionary<string, string>();
foreach (var member in respServerSessionType.GetMembers())
{
if (member is not IMethodSymbol mtdSymbol)
{
continue;
}
// Check that method is declared in RespServerSessionOutput.cs, otherwise we don't consider it a candidate for flagging
if (!member.DeclaringSyntaxReferences.Any(static decl => Path.GetFileName(decl.SyntaxTree.FilePath) == "RespServerSessionOutput.cs"))
{
continue;
}
if (!member.Name.StartsWith("Write"))
{
continue;
}
var tryEquivalent = $"Try{member.Name}";
if (!tryWriteMethods.Contains(tryEquivalent))
{
continue;
}
lookup[tryEquivalent] = member.Name;
}
return lookup;
}
}
/// <summary>
/// Flag all calls to methods looking like RespWriteUtils.TryXXX
/// </summary>
private static void AnalyzeCaller(SyntaxNodeAnalysisContext context, INamedTypeSymbol respWriteUtilsType, Dictionary<string, string> candidateLookup)
{
if (context.Node is not InvocationExpressionSyntax invoke)
{
return;
}
if (invoke.Expression is not MemberAccessExpressionSyntax memberAccess)
{
return;
}
if (memberAccess.Name is not IdentifierNameSyntax methodName)
{
return;
}
// Quickly filter out anything that doesn't look like a TryXXX method
if (string.IsNullOrEmpty(methodName.Identifier.Text) || !methodName.Identifier.Text.StartsWith("Try"))
{
return;
}
// Filter out anything in RespServerSessionOutput.cs, as it's intended to use these methods
if (Path.GetFileName(context.Node.SyntaxTree.FilePath) == "RespServerSessionOutput.cs")
{
return;
}
// Ignore anything that isn't a call to RespWriteUtils
var leftHandType = context.SemanticModel.GetSymbolInfo(memberAccess.Expression);
if (leftHandType.Symbol is null || !SymbolEqualityComparer.Default.Equals(leftHandType.Symbol, respWriteUtilsType))
{
return;
}
// Lookup equivalent method, and raise diagnostic
if (!candidateLookup.TryGetValue(methodName.Identifier.Text, out var rewriteTo))
{
return;
}
// Raise the actual diagnostic
var diag = Diagnostic.Create(AvoidRespWriteUtils, context.Node.GetLocation(), rewriteTo, methodName.Identifier.Text);
context.ReportDiagnostic(diag);
}
}
}