Skip to content

Commit cfd5e8b

Browse files
committed
MA0003 exclude meaningless parameters
1 parent 3e8cecf commit cfd5e8b

File tree

2 files changed

+87
-11
lines changed

2 files changed

+87
-11
lines changed

src/Meziantou.Analyzer/Rules/NamedParameterAnalyzer.cs

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22
using System.Collections.Immutable;
33
using System.Globalization;
44
using System.Linq.Expressions;
@@ -69,19 +69,27 @@ public override void Initialize(AnalysisContext context)
6969

7070
if (callerMustUseNamedArgumentType is not null)
7171
{
72-
var operation = syntaxContext.SemanticModel.GetOperation(argument, syntaxContext.CancellationToken) as IArgumentOperation;
73-
if ((operation?.Parameter) is not null)
72+
if (IsCallerMustUseNamedArgumentAttribute(syntaxContext, argument, callerMustUseNamedArgumentType))
7473
{
75-
var attribute = operation.Parameter.GetAttribute(callerMustUseNamedArgumentType);
76-
if (attribute is not null)
74+
syntaxContext.ReportDiagnostic(Diagnostic.Create(Rule, syntaxContext.Node.GetLocation(), effectiveSeverity: DiagnosticSeverity.Warning, additionalLocations: null, properties: null));
75+
return;
76+
}
77+
78+
static bool IsCallerMustUseNamedArgumentAttribute(SyntaxNodeAnalysisContext context, SyntaxNode argument, INamedTypeSymbol callerMustUseNamedArgumentType)
79+
{
80+
var operation = context.SemanticModel.GetOperation(argument, context.CancellationToken) as IArgumentOperation;
81+
if ((operation?.Parameter) is not null)
7782
{
78-
var requireNamedArgument = attribute.ConstructorArguments.Length == 0 || attribute.ConstructorArguments[0].Value is true;
79-
if (requireNamedArgument)
83+
var attribute = operation.Parameter.GetAttribute(callerMustUseNamedArgumentType);
84+
if (attribute is not null)
8085
{
81-
syntaxContext.ReportDiagnostic(Diagnostic.Create(Rule, syntaxContext.Node.GetLocation(), effectiveSeverity: DiagnosticSeverity.Warning, additionalLocations: null, properties: null));
82-
return;
86+
var requireNamedArgument = attribute.ConstructorArguments.Length == 0 || attribute.ConstructorArguments[0].Value is true;
87+
if (requireNamedArgument)
88+
return true;
8389
}
8490
}
91+
92+
return false;
8593
}
8694
}
8795

@@ -114,6 +122,15 @@ public override void Initialize(AnalysisContext context)
114122
if (argument.Parent.IsKind(SyntaxKind.TupleExpression))
115123
return; // Don't consider tuple
116124

125+
126+
var operation = syntaxContext.SemanticModel.GetOperation(argument, syntaxContext.CancellationToken) as IArgumentOperation;
127+
if (operation?.Parameter is not null)
128+
{
129+
var parameterName = operation.Parameter.Name;
130+
if (!IsMeaningfulParameterName(parameterName))
131+
return;
132+
}
133+
117134
// Exclude in some methods such as ConfigureAwait(false)
118135
var invocationExpression = argument.FirstAncestorOrSelf<ExpressionSyntax>(t => t.IsKind(SyntaxKind.InvocationExpression) || t.IsKind(SyntaxKind.ObjectCreationExpression) || t.IsKind(SyntaxKind.ElementAccessExpression));
119136
if (invocationExpression is not null)
@@ -247,7 +264,6 @@ bool IsParams(SyntaxNode node)
247264
if (invokedMethodSymbol.Name.StartsWith("With", StringComparison.Ordinal) && invokedMethodSymbol.ContainingType.IsOrInheritFrom(syntaxNodeType))
248265
return;
249266

250-
var operation = syntaxContext.SemanticModel.GetOperation(argument, syntaxContext.CancellationToken);
251267
if (operation is not null && operationUtilities.IsInExpressionContext(operation))
252268
return;
253269

@@ -323,6 +339,32 @@ private static bool MustCheckExpressionKind(SyntaxNodeAnalysisContext context, S
323339
return (options & kind) == kind;
324340
}
325341

342+
private static bool IsMeaningfulParameterName(string parameterName)
343+
{
344+
if (string.IsNullOrEmpty(parameterName))
345+
return false;
346+
347+
if (parameterName is "obj")
348+
return false;
349+
350+
// arg, arg1, arg2, etc. are not meaningful
351+
if (parameterName.StartsWith("arg", StringComparison.OrdinalIgnoreCase) && IsAllDigit(parameterName.AsSpan(3)))
352+
return false;
353+
354+
return true;
355+
356+
static bool IsAllDigit(ReadOnlySpan<char> span)
357+
{
358+
for (var i = 0; i < span.Length; i++)
359+
{
360+
if (!char.IsDigit(span[i]))
361+
return false;
362+
}
363+
364+
return true;
365+
}
366+
}
367+
326368
[Flags]
327369
private enum ArgumentExpressionKinds
328370
{

tests/Meziantou.Analyzer.Test/Rules/NamedParameterAnalyzerTests.cs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System.Threading.Tasks;
1+
using System.Threading.Tasks;
22
using Meziantou.Analyzer.Rules;
33
using Meziantou.Analyzer.Test.Helpers;
44
using TestHelper;
@@ -1082,4 +1082,38 @@ void A()
10821082
")
10831083
.ValidateAsync();
10841084
}
1085+
1086+
[Fact]
1087+
public async Task System_Action_1()
1088+
{
1089+
await CreateProjectBuilder()
1090+
.WithSourceCode("""
1091+
class Test
1092+
{
1093+
void A()
1094+
{
1095+
System.Action<string> action = null;
1096+
action(null);
1097+
}
1098+
}
1099+
""")
1100+
.ValidateAsync();
1101+
}
1102+
1103+
[Fact]
1104+
public async Task System_Action_2()
1105+
{
1106+
await CreateProjectBuilder()
1107+
.WithSourceCode("""
1108+
class Test
1109+
{
1110+
void A()
1111+
{
1112+
System.Action<string, string> action = null;
1113+
action(null, null);
1114+
}
1115+
}
1116+
""")
1117+
.ValidateAsync();
1118+
}
10851119
}

0 commit comments

Comments
 (0)