Skip to content

Commit ef9493e

Browse files
paulirwinclaude
andcommitted
Skip char overloads in LuceneDev6001/6002
The analyzer's literal-only guard only suppressed 6001/6002 when the first argument was a `'x'` char literal or `"x"` single-character string literal. Calls like `text.IndexOf(c)` where `c` is a char variable bound to `string.IndexOf(char)` — which has no StringComparison sibling — falsely tripped 6001 (#1211). Filter both the resolved-symbol and ambiguous-candidate paths to overloads whose first parameter is `System.String`, so char overloads are excluded from the rule entirely instead of relying on syntactic literal recognition. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 60a1945 commit ef9493e

2 files changed

Lines changed: 84 additions & 2 deletions

File tree

src/Lucene.Net.CodeAnalysis.Dev/LuceneDev6xxx/LuceneDev6001_6002_StringComparisonAnalyzer.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,13 +118,21 @@ static bool HasStringComparisonParameter(IMethodSymbol? m, INamedTypeSymbol scTy
118118
var (hasStringComparisonArg, isValidValue, invalidArgLocation, comparisonValueName) =
119119
CheckStringComparisonArgument(invocation, semantic, stringComparisonType);
120120

121+
// The rule only applies to overloads whose first parameter is a string —
122+
// overloads like IndexOf(char) / StartsWith(char) have no StringComparison sibling.
123+
static bool FirstParameterIsString(IMethodSymbol? m)
124+
=> m != null && m.Parameters.Length > 0 && m.Parameters[0].Type.SpecialType == SpecialType.System_String;
125+
121126
// If resolved symbol available
122127
if (methodSymbol != null)
123128
{
124129
// Only apply rule to System.String or J2N.StringBuilderExtensions containing type
125130
if (!ContainingTypeIsStringOrJ2N(methodSymbol.ContainingType))
126131
return;
127132

133+
if (!FirstParameterIsString(methodSymbol))
134+
return;
135+
128136
// If the method has StringComparison parameter in signature
129137
bool methodHasComparisonParam = HasStringComparisonParameter(methodSymbol, stringComparisonType);
130138

@@ -160,9 +168,9 @@ static bool HasStringComparisonParameter(IMethodSymbol? m, INamedTypeSymbol scTy
160168
// Handle ambiguous candidates
161169
if (candidateSymbols.Length > 0)
162170
{
163-
// Check if any candidate is from String or J2N types
171+
// Check if any candidate is from String or J2N types and takes a string first parameter
164172
var relevantCandidates = candidateSymbols
165-
.Where(c => ContainingTypeIsStringOrJ2N(c.ContainingType))
173+
.Where(c => ContainingTypeIsStringOrJ2N(c.ContainingType) && FirstParameterIsString(c))
166174
.ToImmutableArray();
167175

168176
if (relevantCandidates.Length == 0)

tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev6xxx/TestLuceneDev6001_6002_StringComparisonAnalyzer.cs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,80 @@ public void M()
7878
await test.RunAsync();
7979
}
8080

81+
[Test]
82+
public async Task Skips_IndexOf_CharLiteral()
83+
{
84+
var testCode = @"
85+
using System;
86+
87+
public class Sample
88+
{
89+
public void M()
90+
{
91+
string text = ""Hello"";
92+
int index = text.IndexOf('H');
93+
}
94+
}";
95+
96+
var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev6001_6002_StringComparisonAnalyzer())
97+
{
98+
TestCode = testCode,
99+
ExpectedDiagnostics = { } // IndexOf(char) has no StringComparison overload; no diagnostic
100+
};
101+
102+
await test.RunAsync();
103+
}
104+
105+
[Test]
106+
public async Task Skips_IndexOf_CharVariable()
107+
{
108+
var testCode = @"
109+
using System;
110+
111+
public class Sample
112+
{
113+
public void M()
114+
{
115+
string text = ""Hello"";
116+
char c = 'H';
117+
int index = text.IndexOf(c);
118+
}
119+
}";
120+
121+
var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev6001_6002_StringComparisonAnalyzer())
122+
{
123+
TestCode = testCode,
124+
ExpectedDiagnostics = { } // IndexOf(char) has no StringComparison overload; no diagnostic
125+
};
126+
127+
await test.RunAsync();
128+
}
129+
130+
[Test]
131+
public async Task Skips_StartsWith_CharVariable()
132+
{
133+
var testCode = @"
134+
using System;
135+
136+
public class Sample
137+
{
138+
public void M()
139+
{
140+
string text = ""Hello"";
141+
char c = 'H';
142+
bool starts = text.StartsWith(c);
143+
}
144+
}";
145+
146+
var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev6001_6002_StringComparisonAnalyzer())
147+
{
148+
TestCode = testCode,
149+
ExpectedDiagnostics = { } // StartsWith(char) has no StringComparison overload; no diagnostic
150+
};
151+
152+
await test.RunAsync();
153+
}
154+
81155
[Test]
82156
public async Task Detects_IndexOf_MissingStringComparison()
83157
{

0 commit comments

Comments
 (0)