Skip to content

Commit ac199fb

Browse files
Refactor analyzers for clarity and maintainability
Refactor several Roslyn analyzer classes to improve readability and robustness. Changes include restructuring conditional logic, adopting early returns, and reducing nesting. These updates clarify checks for method names and types, streamline pattern detection in SplitAnalyzer, and simplify variable reference logic in StringConcatenationAnalyzer. No functional behavior is changed; code is now easier to read and maintain.
1 parent 49aff3a commit ac199fb

5 files changed

Lines changed: 154 additions & 139 deletions

Analyzers/IndexOfSubstringAnalyzer.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -91,16 +91,16 @@ private static void AnalyzeExpressionBody()
9191

9292
private static bool IsIndexOfCall(InvocationExpressionSyntax invocation, SyntaxNodeAnalysisContext context)
9393
{
94-
if (invocation.Expression is MemberAccessExpressionSyntax memberAccess &&
95-
(memberAccess.Name.Identifier.Text == "IndexOf" ||
96-
memberAccess.Name.Identifier.Text == "LastIndexOf"))
94+
if (invocation.Expression is MemberAccessExpressionSyntax memberAccess
95+
&& (memberAccess.Name.Identifier.Text == "IndexOf"
96+
|| memberAccess.Name.Identifier.Text == "LastIndexOf"))
9797
{
9898
var symbolInfo = context.SemanticModel.GetSymbolInfo(memberAccess, context.CancellationToken);
9999
if (symbolInfo.Symbol is IMethodSymbol methodSymbol)
100100
{
101101
var containingType = methodSymbol.ContainingType;
102-
return containingType?.SpecialType == SpecialType.System_String ||
103-
containingType?.ToString() == "Microsoft.Extensions.Primitives.StringSegment";
102+
return containingType?.SpecialType == SpecialType.System_String
103+
|| containingType?.ToString() == "Microsoft.Extensions.Primitives.StringSegment";
104104
}
105105
}
106106
return false;
@@ -117,8 +117,8 @@ private static bool FindSubstringUsage(
117117
{
118118
foreach (var invocation in statement.DescendantNodes().OfType<InvocationExpressionSyntax>())
119119
{
120-
if (invocation.Expression is MemberAccessExpressionSyntax memberAccess &&
121-
memberAccess.Name.Identifier.Text == "Substring")
120+
if (invocation.Expression is MemberAccessExpressionSyntax memberAccess
121+
&& memberAccess.Name.Identifier.Text == "Substring")
122122
{
123123
// Check if any argument references our variable
124124
foreach (var arg in invocation.ArgumentList.Arguments)

Analyzers/SplitAnalyzer.cs

Lines changed: 83 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -30,63 +30,62 @@ public override void Initialize(AnalysisContext context)
3030
private static void AnalyzeInvocation(SyntaxNodeAnalysisContext context)
3131
{
3232
var invocation = (InvocationExpressionSyntax)context.Node;
33+
if (invocation.Expression is not MemberAccessExpressionSyntax memberAccess)
34+
{
35+
return;
36+
}
3337

34-
if (invocation.Expression is MemberAccessExpressionSyntax memberAccess)
38+
// Check for .Split().First() or .Split().FirstOrDefault()
39+
if (memberAccess.Name.Identifier.Text is "First" or "FirstOrDefault"
40+
&& memberAccess.Expression is InvocationExpressionSyntax innerInvocation
41+
&& innerInvocation.Expression is MemberAccessExpressionSyntax innerMemberAccess
42+
&& innerMemberAccess.Name.Identifier.Text == "Split")
3543
{
36-
// Check for .Split().First() or .Split().FirstOrDefault()
37-
if (memberAccess.Name.Identifier.Text is "First" or "FirstOrDefault")
44+
var symbolInfo = context.SemanticModel.GetSymbolInfo(innerMemberAccess, context.CancellationToken);
45+
if (symbolInfo.Symbol is IMethodSymbol innerMethod
46+
&& innerMethod.ContainingType?.SpecialType == SpecialType.System_String)
3847
{
39-
if (memberAccess.Expression is InvocationExpressionSyntax innerInvocation &&
40-
innerInvocation.Expression is MemberAccessExpressionSyntax innerMemberAccess &&
41-
innerMemberAccess.Name.Identifier.Text == "Split")
42-
{
43-
var symbolInfo = context.SemanticModel.GetSymbolInfo(innerMemberAccess, context.CancellationToken);
44-
if (symbolInfo.Symbol is IMethodSymbol innerMethod &&
45-
innerMethod.ContainingType?.SpecialType == SpecialType.System_String)
46-
{
47-
var argsString = GetArgumentsString(innerInvocation.ArgumentList.Arguments);
48-
var diagnostic = Diagnostic.Create(
49-
DiagnosticDescriptors.UseFirstSplitInsteadOfSplitFirst,
50-
invocation.GetLocation(),
51-
argsString);
52-
context.ReportDiagnostic(diagnostic);
53-
return;
54-
}
55-
}
48+
var argsString = GetArgumentsString(innerInvocation.ArgumentList.Arguments);
49+
var diagnostic = Diagnostic.Create(
50+
DiagnosticDescriptors.UseFirstSplitInsteadOfSplitFirst,
51+
invocation.GetLocation(),
52+
argsString);
53+
context.ReportDiagnostic(diagnostic);
54+
return;
5655
}
56+
}
57+
58+
// Check for .Split() that should use SplitAsSegments or SplitToEnumerable
59+
if (memberAccess.Name.Identifier.Text == "Split")
60+
{
61+
var symbolInfo = context.SemanticModel.GetSymbolInfo(memberAccess, context.CancellationToken);
62+
if (symbolInfo.Symbol is not IMethodSymbol methodSymbol)
63+
return;
5764

58-
// Check for .Split() that should use SplitAsSegments or SplitToEnumerable
59-
if (memberAccess.Name.Identifier.Text == "Split")
65+
if (methodSymbol.ContainingType?.SpecialType != SpecialType.System_String)
66+
return;
67+
68+
// Check how the result is used
69+
var isUsedInLoop = IsUsedInForeachLoop(invocation);
70+
var argsString = GetArgumentsString(invocation.ArgumentList.Arguments);
71+
72+
if (isUsedInLoop)
73+
{
74+
// Suggest SplitToEnumerable for foreach loops
75+
var diagnostic = Diagnostic.Create(
76+
DiagnosticDescriptors.UseSplitToEnumerable,
77+
invocation.GetLocation(),
78+
argsString);
79+
context.ReportDiagnostic(diagnostic);
80+
}
81+
else
6082
{
61-
var symbolInfo = context.SemanticModel.GetSymbolInfo(memberAccess, context.CancellationToken);
62-
if (symbolInfo.Symbol is not IMethodSymbol methodSymbol)
63-
return;
64-
65-
if (methodSymbol.ContainingType?.SpecialType != SpecialType.System_String)
66-
return;
67-
68-
// Check how the result is used
69-
var isUsedInLoop = IsUsedInForeachLoop(invocation);
70-
var argsString = GetArgumentsString(invocation.ArgumentList.Arguments);
71-
72-
if (isUsedInLoop)
73-
{
74-
// Suggest SplitToEnumerable for foreach loops
75-
var diagnostic = Diagnostic.Create(
76-
DiagnosticDescriptors.UseSplitToEnumerable,
77-
invocation.GetLocation(),
78-
argsString);
79-
context.ReportDiagnostic(diagnostic);
80-
}
81-
else
82-
{
83-
// General suggestion for SplitAsSegments
84-
var diagnostic = Diagnostic.Create(
85-
DiagnosticDescriptors.UseSplitAsSegments,
86-
invocation.GetLocation(),
87-
argsString);
88-
context.ReportDiagnostic(diagnostic);
89-
}
83+
// General suggestion for SplitAsSegments
84+
var diagnostic = Diagnostic.Create(
85+
DiagnosticDescriptors.UseSplitAsSegments,
86+
invocation.GetLocation(),
87+
argsString);
88+
context.ReportDiagnostic(diagnostic);
9089
}
9190
}
9291
}
@@ -96,40 +95,48 @@ private static void AnalyzeElementAccess(SyntaxNodeAnalysisContext context)
9695
var elementAccess = (ElementAccessExpressionSyntax)context.Node;
9796

9897
// Check for .Split()[0] or .Split()[index]
99-
if (elementAccess.Expression is InvocationExpressionSyntax invocation &&
100-
invocation.Expression is MemberAccessExpressionSyntax memberAccess &&
101-
memberAccess.Name.Identifier.Text == "Split")
98+
if (elementAccess.Expression is not InvocationExpressionSyntax invocation
99+
|| invocation.Expression is not MemberAccessExpressionSyntax memberAccess
100+
|| memberAccess.Name.Identifier.Text != "Split")
102101
{
103-
var symbolInfo = context.SemanticModel.GetSymbolInfo(memberAccess, context.CancellationToken);
104-
if (symbolInfo.Symbol is IMethodSymbol methodSymbol &&
105-
methodSymbol.ContainingType?.SpecialType == SpecialType.System_String)
106-
{
107-
// Check if accessing index 0
108-
if (elementAccess.ArgumentList.Arguments.Count == 1)
109-
{
110-
var argument = elementAccess.ArgumentList.Arguments[0];
111-
if (argument.Expression is LiteralExpressionSyntax literal &&
112-
literal.Token.ValueText == "0")
113-
{
114-
var argsString = GetArgumentsString(invocation.ArgumentList.Arguments);
115-
var diagnostic = Diagnostic.Create(
116-
DiagnosticDescriptors.UseFirstSplitInsteadOfSplitFirst,
117-
elementAccess.GetLocation(),
118-
argsString);
119-
context.ReportDiagnostic(diagnostic);
120-
}
121-
}
122-
}
102+
return;
103+
}
104+
105+
var symbolInfo = context.SemanticModel.GetSymbolInfo(memberAccess, context.CancellationToken);
106+
if (symbolInfo.Symbol is not IMethodSymbol methodSymbol
107+
|| methodSymbol.ContainingType?.SpecialType != SpecialType.System_String)
108+
{
109+
return;
123110
}
111+
112+
// Check if accessing index 0
113+
if (elementAccess.ArgumentList.Arguments.Count != 1)
114+
{
115+
return;
116+
}
117+
118+
var argument = elementAccess.ArgumentList.Arguments[0];
119+
if (argument.Expression is not LiteralExpressionSyntax literal
120+
|| literal.Token.ValueText != "0")
121+
{
122+
return;
123+
}
124+
125+
var argsString = GetArgumentsString(invocation.ArgumentList.Arguments);
126+
var diagnostic = Diagnostic.Create(
127+
DiagnosticDescriptors.UseFirstSplitInsteadOfSplitFirst,
128+
elementAccess.GetLocation(),
129+
argsString);
130+
context.ReportDiagnostic(diagnostic);
124131
}
125132

126133
private static bool IsUsedInForeachLoop(SyntaxNode node)
127134
{
128135
var parent = node.Parent;
129136
while (parent != null)
130137
{
131-
if (parent is ForEachStatementSyntax foreachStatement &&
132-
foreachStatement.Expression.Contains(node))
138+
if (parent is ForEachStatementSyntax foreachStatement
139+
&& foreachStatement.Expression.Contains(node))
133140
{
134141
return true;
135142
}

Analyzers/StringComparablePatternMatchingSuppressor.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,8 @@ private static bool ShouldSuppressForBinaryExpression(BinaryExpressionSyntax bin
110110
return false;
111111

112112
// For || or && expressions, check both sides
113-
if (binaryExpression.IsKind(SyntaxKind.LogicalOrExpression) ||
114-
binaryExpression.IsKind(SyntaxKind.LogicalAndExpression))
113+
if (binaryExpression.IsKind(SyntaxKind.LogicalOrExpression)
114+
|| binaryExpression.IsKind(SyntaxKind.LogicalAndExpression))
115115
{
116116
// Check if any child expression involves StringComparable
117117
return ContainsStringComparableComparison(binaryExpression, semanticModel, context);
@@ -131,9 +131,9 @@ private static bool ContainsStringComparableComparison(SyntaxNode node, Semantic
131131
// Recursively check all binary expressions
132132
foreach (var descendant in node.DescendantNodesAndSelf())
133133
{
134-
if (descendant is BinaryExpressionSyntax binaryExpr &&
135-
(binaryExpr.IsKind(SyntaxKind.EqualsExpression) ||
136-
binaryExpr.IsKind(SyntaxKind.NotEqualsExpression)))
134+
if (descendant is BinaryExpressionSyntax binaryExpr
135+
&& (binaryExpr.IsKind(SyntaxKind.EqualsExpression)
136+
|| binaryExpr.IsKind(SyntaxKind.NotEqualsExpression)))
137137
{
138138
var leftType = semanticModel.GetTypeInfo(binaryExpr.Left, context.CancellationToken).Type;
139139
if (leftType != null)

Analyzers/StringConcatenationAnalyzer.cs

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -53,19 +53,18 @@ private static void AnalyzeAssignment(SyntaxNodeAnalysisContext context)
5353
return;
5454

5555
// Check if right side involves the same variable (e.g., str = str + "...")
56-
if (assignment.Right is BinaryExpressionSyntax binaryExpr &&
57-
binaryExpr.IsKind(SyntaxKind.AddExpression))
56+
if (assignment.Right is not BinaryExpressionSyntax binaryExpr
57+
|| !binaryExpr.IsKind(SyntaxKind.AddExpression)
58+
|| !ReferencesVariable(binaryExpr.Left, assignment.Left, context)
59+
&& !ReferencesVariable(binaryExpr.Right, assignment.Left, context))
5860
{
59-
// Check if either side of the addition references the same variable
60-
if (ReferencesVariable(binaryExpr.Left, assignment.Left, context) ||
61-
ReferencesVariable(binaryExpr.Right, assignment.Left, context))
62-
{
63-
var diagnostic = Diagnostic.Create(
64-
DiagnosticDescriptors.UseStringBuilderInLoop,
65-
assignment.GetLocation());
66-
context.ReportDiagnostic(diagnostic);
67-
}
61+
return;
6862
}
63+
64+
var diagnostic = Diagnostic.Create(
65+
DiagnosticDescriptors.UseStringBuilderInLoop,
66+
assignment.GetLocation());
67+
context.ReportDiagnostic(diagnostic);
6968
}
7069
}
7170

@@ -89,12 +88,14 @@ private static bool IsInLoop(SyntaxNode node)
8988

9089
private static bool ReferencesVariable(SyntaxNode expression, SyntaxNode variable, SyntaxNodeAnalysisContext context)
9190
{
92-
if (expression is IdentifierNameSyntax identifier && variable is IdentifierNameSyntax varIdentifier)
91+
if (expression is not IdentifierNameSyntax identifier
92+
|| variable is not IdentifierNameSyntax varIdentifier)
9393
{
94-
var exprSymbol = context.SemanticModel.GetSymbolInfo(identifier, context.CancellationToken).Symbol;
95-
var varSymbol = context.SemanticModel.GetSymbolInfo(varIdentifier, context.CancellationToken).Symbol;
96-
return SymbolEqualityComparer.Default.Equals(exprSymbol, varSymbol);
94+
return false;
9795
}
98-
return false;
96+
97+
var exprSymbol = context.SemanticModel.GetSymbolInfo(identifier, context.CancellationToken).Symbol;
98+
var varSymbol = context.SemanticModel.GetSymbolInfo(varIdentifier, context.CancellationToken).Symbol;
99+
return SymbolEqualityComparer.Default.Equals(exprSymbol, varSymbol);
99100
}
100101
}

0 commit comments

Comments
 (0)