Add clearBufferInterval
parameter into DelayedActionRenderer<T>()
constructor
1 |
using System.Collections.Immutable; |
|
2 |
using System.Linq; |
|
3 |
using Libplanet.Action; |
|
4 |
using Microsoft.CodeAnalysis; |
|
5 |
using Microsoft.CodeAnalysis.Diagnostics; |
|
6 |
using Microsoft.CodeAnalysis.Operations; |
|
7 |
|
|
8 |
namespace Libplanet.Analyzers |
|
9 |
{
|
|
10 |
[DiagnosticAnalyzer(
|
|
11 |
LanguageNames.CSharp,
|
|
12 |
LanguageNames.FSharp,
|
|
13 |
LanguageNames.VisualBasic
|
|
14 |
)]
|
|
15 |
public class ActionAnalyzer : DiagnosticAnalyzer |
|
16 |
{
|
|
17 |
public const string IdPrefix = "LAA"; |
|
18 |
|
|
19 |
#pragma warning disable S1075
|
|
20 |
public const string HelpLinkUriPrefix = |
|
21 |
"https://github.com/planetarium/libplanet/blob/main/Libplanet.Analyzers/rules/LAA"; |
|
22 |
#pragma warning restore S1075
|
|
23 |
|
|
24 |
#pragma warning disable SA1118
|
|
25 | 1 |
private static readonly DiagnosticDescriptor[] Diagnostics = |
26 | 1 |
{
|
27 | 1 |
new DiagnosticDescriptor( |
28 | 1 |
id: $"{IdPrefix}9999", |
29 | 1 |
title: "DebugMessageWhichShouldNotAppearToUsers", |
30 | 1 |
messageFormat: "DEBUG: {0}", |
31 | 1 |
category: "Debug", |
32 | 1 |
defaultSeverity: DiagnosticSeverity.Hidden, |
33 | 1 |
isEnabledByDefault: true |
34 | 1 |
),
|
35 | 1 |
new DiagnosticDescriptor( |
36 | 1 |
id: $"{IdPrefix}1001", |
37 | 1 |
title: "SystemRandomBreaksActionDeterminism", |
38 | 1 |
messageFormat: |
39 | 1 |
$"The {{0}} makes an {nameof(IAction)} indeterministic; use " + |
40 | 1 |
$"{nameof(IActionContext)}.{nameof(IActionContext.Random)} property instead.", |
41 | 1 |
category: "Determinism", |
42 | 1 |
defaultSeverity: DiagnosticSeverity.Warning, |
43 | 1 |
isEnabledByDefault: true, |
44 | 1 |
helpLinkUri: $"{HelpLinkUriPrefix}1001.md" |
45 | 1 |
),
|
46 | 1 |
new DiagnosticDescriptor( |
47 | 1 |
id: $"{IdPrefix}1002", |
48 | 1 |
title: "DictionariesOrSetsShouldBeOrderedToEnumerate", |
49 | 1 |
messageFormat: "Enumerating an instance of {0} is indeterministic since " + |
50 | 1 |
"the order of {0} is unspecified; explicitly sort them before {1}.", |
51 | 1 |
category: "Determinism", |
52 | 1 |
defaultSeverity: DiagnosticSeverity.Warning, |
53 | 1 |
isEnabledByDefault: true, |
54 | 1 |
helpLinkUri: $"{HelpLinkUriPrefix}1002.md" |
55 | 1 |
),
|
56 | 1 |
};
|
57 |
#pragma warning restore SA1118
|
|
58 |
|
|
59 |
public static IImmutableDictionary<string, DiagnosticDescriptor> Rules => |
|
60 | 1 |
Diagnostics.ToImmutableDictionary(d => d.Id, d => d); |
61 |
|
|
62 |
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => |
|
63 | 1 |
Diagnostics.ToImmutableArray(); |
64 |
|
|
65 |
public override void Initialize(AnalysisContext context) |
|
66 | 1 |
{
|
67 | 1 |
context.EnableConcurrentExecution(); |
68 | 1 |
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze); |
69 | 1 |
context.RegisterOperationAction(LAA1001_SystemRandom, OperationKind.ObjectCreation); |
70 | 1 |
context.RegisterOperationAction( |
71 | 1 |
LAA1002_DictionarySetEnumeration, |
72 | 1 |
OperationKind.Conversion, |
73 | 1 |
OperationKind.Loop |
74 | 1 |
);
|
75 | 1 |
}
|
76 |
|
|
77 |
private static void LAA1001_SystemRandom(OperationAnalysisContext context) |
|
78 | 1 |
{
|
79 | 1 |
if (context.Operation is IObjectCreationOperation op && |
80 | 1 |
context.Compilation.GetTypeByMetadataName("System.Random") is ITypeSymbol ts && |
81 | 1 |
op.Type is ITypeSymbol opType && |
82 | 1 |
SymbolEqualityComparer.Default.Equals(opType, ts)) |
83 | 1 |
{
|
84 | 1 |
Diagnostic diag = Diagnostic.Create( |
85 | 1 |
Rules[$"{IdPrefix}1001"], |
86 | 1 |
op.Syntax.GetLocation(), |
87 | 1 |
ts.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat) |
88 | 1 |
);
|
89 | 1 |
context.ReportDiagnostic(diag); |
90 | 1 |
}
|
91 | 1 |
}
|
92 |
|
|
93 |
private static void LAA1002_DictionarySetEnumeration(OperationAnalysisContext context) |
|
94 | 1 |
{
|
95 |
const string ns = "System.Collections"; |
|
96 | 1 |
SymbolDisplayFormat symbolFormat = SymbolDisplayFormat.CSharpErrorMessageFormat; |
97 | 1 |
Compilation comp = context.Compilation; |
98 | 1 |
SymbolEqualityComparer comparer = SymbolEqualityComparer.Default; |
99 |
|
|
100 | 1 |
bool TypeEquals(ITypeSymbol? valType, string metadataName) => valType is ITypeSymbol a |
101 | 1 |
&& comp.GetTypeByMetadataName(metadataName) is INamedTypeSymbol b |
102 | 1 |
&& comparer.Equals(a, b); |
103 |
|
|
104 | 1 |
string[] dictOrSets = |
105 | 1 |
{
|
106 | 1 |
$"{ns}.Generic.IDictionary`2", |
107 | 1 |
$"{ns}.Generic.IReadOnlyDictionary`2", |
108 | 1 |
$"{ns}.Immutable.IImmutableDictionary`2", |
109 | 1 |
$"{ns}.IDictionary", |
110 | 1 |
$"{ns}.Generic.ISet`1", |
111 | 1 |
$"{ns}.Immutable.IImmutableSet`1", |
112 | 1 |
};
|
113 |
|
|
114 | 1 |
bool IsDictOrSet(ITypeSymbol? valType) => valType is ITypeSymbol t && |
115 | 1 |
t.OriginalDefinition.AllInterfaces |
116 | 1 |
.OfType<ITypeSymbol>() |
117 | 1 |
.Select(ifce => ifce.OriginalDefinition) |
118 | 1 |
.OfType<ITypeSymbol>() |
119 | 1 |
.Any(i => dictOrSets.Any(dst => TypeEquals(i, dst)) |
120 | 1 |
);
|
121 |
|
|
122 | 1 |
switch (context.Operation) |
123 |
{
|
|
124 |
case IConversionOperation conv: |
|
125 | 1 |
{
|
126 | 1 |
ITypeSymbol? endType = conv.Type; |
127 | 1 |
if (!TypeEquals(endType?.OriginalDefinition, $"{ns}.Generic.IEnumerable`1") && |
128 | 1 |
!TypeEquals(endType, $"{ns}.IEnumerable")) |
129 | 1 |
{
|
130 | 1 |
return; |
131 |
}
|
|
132 |
|
|
133 | 1 |
if (conv.Parent is IArgumentOperation arg && |
134 | 1 |
arg.Parent is IInvocationOperation invoke && |
135 | 1 |
invoke.TargetMethod.IsGenericMethod && |
136 | 1 |
invoke.TargetMethod.OriginalDefinition is IMethodSymbol proto) |
137 | 1 |
{
|
138 | 1 |
var method = invoke.TargetMethod.Name; |
139 | 1 |
if (method.StartsWith("OrderBy") && |
140 | 1 |
TypeEquals(proto.ContainingType, "System.Linq.Enumerable") && |
141 | 1 |
TypeEquals( |
142 | 1 |
proto.ReturnType.OriginalDefinition, |
143 | 1 |
"System.Linq.IOrderedEnumerable`1"
|
144 | 1 |
))
|
145 | 1 |
{
|
146 |
// Ignores Linq's .OrderBy()/OrderByDescending() methods.
|
|
147 | 1 |
return; |
148 |
}
|
|
149 | 1 |
else if (method.StartsWith("To") && |
150 | 1 |
(method.EndsWith("Dictionary") || method.EndsWith("Set")) && |
151 | 1 |
IsDictOrSet(proto.ReturnType)) |
152 | 1 |
{
|
153 |
// Ignores .ToDictionary()/ToHashSet() etc.
|
|
154 | 1 |
return; |
155 |
}
|
|
156 | 1 |
}
|
157 |
|
|
158 | 1 |
if (conv.Parent is IArgumentOperation arg1 && |
159 | 1 |
arg1.Parent is IObjectCreationOperation @new && |
160 | 1 |
IsDictOrSet(@new.Type)) |
161 | 1 |
{
|
162 |
// Ignores new Dictionary()/new HashSet() etc.
|
|
163 | 1 |
return; |
164 |
}
|
|
165 |
|
|
166 | 1 |
ITypeSymbol? valType = conv.Operand?.Type; |
167 | 1 |
if (IsDictOrSet(valType)) |
168 | 1 |
{
|
169 | 1 |
string func = "enumerating"; |
170 | 1 |
if (conv.Parent is IArgumentOperation argConv) |
171 | 1 |
{
|
172 | 1 |
func = argConv.Parent switch |
173 | 1 |
{
|
174 | 1 |
IObjectCreationOperation c => |
175 | 1 |
$"passing to {c.Constructor.ToDisplayString(symbolFormat)} " + |
176 | 1 |
"constructor", |
177 | 1 |
IInvocationOperation m => |
178 | 1 |
$"passing to {m.TargetMethod.ToDisplayString(symbolFormat)} " + |
179 | 1 |
"method", |
180 | 1 |
_ => func, |
181 | 1 |
};
|
182 | 1 |
}
|
183 |
|
|
184 | 1 |
Diagnostic diag = Diagnostic.Create( |
185 | 1 |
Rules[$"{IdPrefix}1002"], |
186 | 1 |
conv.Syntax.GetLocation(), |
187 | 1 |
valType!.ToDisplayString(symbolFormat), |
188 | 1 |
func
|
189 | 1 |
);
|
190 | 1 |
context.ReportDiagnostic(diag); |
191 | 1 |
}
|
192 |
|
|
193 | 1 |
break; |
194 |
}
|
|
195 |
|
|
196 |
case IForEachLoopOperation loop: |
|
197 | 1 |
{
|
198 | 1 |
IOperation collection = loop.Collection; |
199 | 1 |
ITypeSymbol? collType = collection.Type; |
200 | 1 |
if (IsDictOrSet(collType)) |
201 | 1 |
{
|
202 | 1 |
Diagnostic diag = Diagnostic.Create( |
203 | 1 |
Rules[$"{IdPrefix}1002"], |
204 | 1 |
collection.Syntax.GetLocation(), |
205 | 1 |
collType.ToDisplayString(symbolFormat), |
206 | 1 |
"iterating via foreach"
|
207 | 1 |
);
|
208 | 1 |
context.ReportDiagnostic(diag); |
209 | 1 |
}
|
210 |
|
|
211 | 1 |
break; |
212 |
}
|
|
213 |
}
|
|
214 | 1 |
}
|
215 |
|
|
216 |
private static Diagnostic Debug(string message) => |
|
217 |
Diagnostic.Create(Rules[$"{IdPrefix}9999"], null, message); |
|
218 |
}
|
|
219 |
}
|
Read our documentation on viewing source code .