-
Notifications
You must be signed in to change notification settings - Fork 0
/
ValueNotUsedSuppressor.cs
115 lines (83 loc) · 4.1 KB
/
ValueNotUsedSuppressor.cs
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
using System.Diagnostics;
using System.Collections.Immutable;
namespace Blokyk.Roslyn;
[DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.FSharp, LanguageNames.VisualBasic)]
public class ValueNotUsedSuppressor : DiagnosticSuppressor
{
private static readonly SuppressionDescriptor _suppressIDE0058Descriptor
= new(
"BLK1001",
"IDE0058",
"This type has a fluent interface and thus the return value can be safely ignored"
);
public override ImmutableArray<SuppressionDescriptor> SupportedSuppressions { get; } = ImmutableArray.Create(_suppressIDE0058Descriptor);
private static readonly HashSet<string> _builtinExceptions = new() {
"System.Text.StringBuilder"
};
private HashSet<string> _exceptionsMetadataNames = _builtinExceptions;
private void SetNewExceptionList(string[] names) {
_exceptionsMetadataNames = new();
foreach (var name in names) {
var trimmedName = name.Trim();
if (!string.IsNullOrEmpty(trimmedName))
_exceptionsMetadataNames.Add(name.Trim());
}
}
private int _currNamesListHash = 0;
private const string _supLogPath = "/home/blokyk/csharp/suppressor.log";
[Conditional("LOG")]
private static void Log(string s) => File.AppendAllText(_supLogPath, DateTime.Now + " > " + s + "\n");
public override void ReportSuppressions(SuppressionAnalysisContext context) {
Log("ReportSuppressions() called...");
foreach (var diag in context.ReportedDiagnostics) {
var tree = diag.Location.SourceTree!;
var opts = context.Options.AnalyzerConfigOptionsProvider.GetOptions(tree);
if (opts.TryGetValue("dotnet_fluent_types", out var names)) {
Log("dotnet_fluent_types = " + names);
if (names.GetHashCode() != _currNamesListHash) {
_currNamesListHash = names.GetHashCode();
SetNewExceptionList(names.Split(','));
}
} else {
_exceptionsMetadataNames = _builtinExceptions;
Log("dotnet_fluent_types doesn't exist!");
Log(string.Join(", ", opts.Keys));
}
Log("fluent_types = [" + string.Join(", ", _exceptionsMetadataNames) + "]");
var node = tree.GetRoot().FindNode(diag.Location.SourceSpan, getInnermostNodeForTie: true);
var model = context.GetSemanticModel(tree);
var nodeSymbolInfo = model.GetSymbolInfo(node).Symbol;
if (nodeSymbolInfo is null)
return;
if (nodeSymbolInfo is not IMethodSymbol methodInfo)
return;
var containingTypeName = GetFullMetadataName(methodInfo.ContainingType);
Log("method's containing type is named '" + containingTypeName + "'");
if (!_exceptionsMetadataNames.Contains(containingTypeName))
return;
Log("type '" + containingTypeName + "' is fluent, suppressing diagnostic...");
context.ReportSuppression(Suppression.Create(_suppressIDE0058Descriptor, diag));
}
}
// from https://stackoverflow.com/questions/27105909/get-fully-qualified-metadata-name-in-roslyn
public static string GetFullMetadataName(ISymbol s) {
if (IsRootNamespace(s)) {
return string.Empty;
}
var parts = new Stack<string>();
parts.Push(s.MetadataName);
var last = s;
s = s.ContainingSymbol;
while (!IsRootNamespace(s)) {
if (s is ITypeSymbol && last is ITypeSymbol) {
parts.Push("+");
} else {
parts.Push(".");
}
parts.Push(s.OriginalDefinition.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat));
s = s.ContainingSymbol;
}
return string.Concat(parts);
}
private static bool IsRootNamespace(ISymbol symbol) => symbol is INamespaceSymbol { IsGlobalNamespace: true };
}