Skip to content

Commit b0d17db

Browse files
ajbargaclaude
andauthored
fix: Report PH2071 diagnostics inline for IDE support (#1084)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent de84bf8 commit b0d17db

File tree

2 files changed

+120
-11
lines changed

2 files changed

+120
-11
lines changed

Philips.CodeAnalysis.DuplicateCodeAnalyzer/AvoidDuplicateCodeAnalyzer.cs

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ public virtual EditorConfigOptions InitializeEditorConfigOptions(out Diagnostic
107107
private sealed class CompilationAnalyzer
108108
{
109109
private readonly DuplicateDetector _library = new();
110-
private readonly List<Diagnostic> _diagnostics = [];
110+
private readonly Diagnostic _configurationError;
111111
private readonly int _duplicateTokenThreshold;
112112
private readonly Helper _helper;
113113
private readonly bool _shouldGenerateExceptionsFile;
@@ -118,10 +118,7 @@ public CompilationAnalyzer(int duplicateTokenThreshold, Helper helper, bool shou
118118
_duplicateTokenThreshold = duplicateTokenThreshold;
119119
_helper = helper;
120120
_shouldGenerateExceptionsFile = shouldGenerateExceptionsFile;
121-
if (configurationError != null)
122-
{
123-
_diagnostics.Add(configurationError);
124-
}
121+
_configurationError = configurationError;
125122
}
126123

127124
private string ToPrettyReference(FileLinePositionSpan fileSpan)
@@ -163,7 +160,7 @@ public void AnalyzeMethod(SyntaxNodeAnalysisContext obj)
163160
{
164161
Location location = evidence.LocationEnvelope.Contents();
165162
Location existingEvidenceLocation = existingEvidence.LocationEnvelope.Contents();
166-
CreateDuplicateDiagnostic(location, existingEvidenceLocation, token, methodDeclarationSyntax);
163+
ReportDuplicateDiagnostic(obj, location, existingEvidenceLocation, token, methodDeclarationSyntax);
167164

168165
// Don't pile on. Move on to the next method.
169166
return;
@@ -177,7 +174,7 @@ public void AnalyzeMethod(SyntaxNodeAnalysisContext obj)
177174
}
178175
}
179176

180-
private void CreateDuplicateDiagnostic(Location location, Location existingEvidenceLocation, SyntaxToken token, MethodDeclarationSyntax methodDeclarationSyntax)
177+
private void ReportDuplicateDiagnostic(SyntaxNodeAnalysisContext context, Location location, Location existingEvidenceLocation, SyntaxToken token, MethodDeclarationSyntax methodDeclarationSyntax)
181178
{
182179
// We found a duplicate, but if it's partially duplicated with itself, ignore it.
183180
if (!location.SourceSpan.IntersectsWith(existingEvidenceLocation.SourceSpan))
@@ -186,7 +183,7 @@ private void CreateDuplicateDiagnostic(Location location, Location existingEvide
186183
FileLinePositionSpan existingEvidenceLineSpan = existingEvidenceLocation.GetLineSpan();
187184
var reference = ToPrettyReference(existingEvidenceLineSpan);
188185

189-
_diagnostics.Add(Diagnostic.Create(Rule, location, new List<Location>() { existingEvidenceLocation }, reference, shapeDetails));
186+
context.ReportDiagnostic(Diagnostic.Create(Rule, location, new List<Location>() { existingEvidenceLocation }, reference, shapeDetails));
190187

191188
if (_shouldGenerateExceptionsFile)
192189
{
@@ -216,14 +213,14 @@ private void CreateExceptionDiagnostic(Exception ex, SyntaxNodeAnalysisContext s
216213
}
217214

218215
Location location = syntaxNodeAnalysisContext.Node.GetLocation();
219-
_diagnostics.Add(Diagnostic.Create(UnhandledExceptionRule, location, result, ex.Message));
216+
syntaxNodeAnalysisContext.ReportDiagnostic(Diagnostic.Create(UnhandledExceptionRule, location, result, ex.Message));
220217
}
221218

222219
public void EndCompilationAction(CompilationAnalysisContext context)
223220
{
224-
foreach (Diagnostic diagnostic in _diagnostics)
221+
if (_configurationError != null)
225222
{
226-
context.ReportDiagnostic(diagnostic);
223+
context.ReportDiagnostic(_configurationError);
227224
}
228225
}
229226

Philips.CodeAnalysis.Test/DuplicateCode/AvoidDuplicateCodeAnalyzerTest.cs

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// © 2019 Koninklijke Philips N.V. See License.md in the project root for license information.
22

33
using System.Collections.Immutable;
4+
using System.IO;
45
using System.Linq;
56
using System.Text.RegularExpressions;
67
using System.Threading.Tasks;
@@ -392,4 +393,115 @@ await VerifyDiagnostic(file,
392393
).ConfigureAwait(false);
393394
}
394395
}
396+
397+
[TestClass]
398+
public class AvoidDuplicateCodeInvalidTokenCountTest : DiagnosticVerifier
399+
{
400+
protected override DiagnosticAnalyzer GetDiagnosticAnalyzer()
401+
{
402+
return new AvoidDuplicateCodeAnalyzer() { DefaultDuplicateTokenThreshold = 100 };
403+
}
404+
405+
protected override ImmutableDictionary<string, string> GetAdditionalAnalyzerConfigOptions()
406+
{
407+
return base.GetAdditionalAnalyzerConfigOptions()
408+
.Add($@"dotnet_code_quality.{AvoidDuplicateCodeAnalyzer.Rule.Id}.token_count", @"abc");
409+
}
410+
411+
[TestMethod]
412+
[TestCategory(TestDefinitions.UnitTests)]
413+
public async Task AvoidDuplicateCodeReportsInvalidTokenCountAsync()
414+
{
415+
var source = @"
416+
namespace MyNamespace
417+
{{
418+
class FooClass
419+
{{
420+
public void Foo()
421+
{{
422+
object obj = new object();
423+
}}
424+
}}
425+
}}
426+
";
427+
428+
await VerifyDiagnostic(source,
429+
new DiagnosticResult()
430+
{
431+
Id = AvoidDuplicateCodeAnalyzer.Rule.Id,
432+
Message = new Regex(".+invalid.+"),
433+
Severity = DiagnosticSeverity.Error,
434+
Locations = [],
435+
}
436+
).ConfigureAwait(false);
437+
}
438+
}
439+
440+
[TestClass]
441+
public class AvoidDuplicateCodeGenerateExceptionsFileTest : AvoidDuplicateCodeAnalyzerTest
442+
{
443+
private const string GeneratedFileName = @"DuplicateCode.Allowed.GENERATED.txt";
444+
445+
protected override ImmutableDictionary<string, string> GetAdditionalAnalyzerConfigOptions()
446+
{
447+
return base.GetAdditionalAnalyzerConfigOptions()
448+
.Add($@"dotnet_code_quality.{AvoidDuplicateCodeAnalyzer.Rule.Id}.generate_exceptions_file", @"true");
449+
}
450+
451+
[TestMethod]
452+
[TestCategory(TestDefinitions.UnitTests)]
453+
public async Task AvoidDuplicateCodeGeneratesExceptionsFileAsync()
454+
{
455+
// Ensure clean state
456+
if (File.Exists(GeneratedFileName))
457+
{
458+
File.Delete(GeneratedFileName);
459+
}
460+
461+
var source = @"
462+
namespace MyNamespace
463+
{{
464+
class FooClass
465+
{{
466+
public void Foo()
467+
{{
468+
object obj = new object(); object obj2 = new object(); object obj3 = new object();
469+
}}
470+
public void Bar()
471+
{{
472+
object obj = new object(); object obj2 = new object(); object obj3 = new object();
473+
}}
474+
}}
475+
}}
476+
";
477+
478+
try
479+
{
480+
await VerifyDiagnostic(source,
481+
new DiagnosticResult()
482+
{
483+
Id = AvoidDuplicateCodeAnalyzer.Rule.Id,
484+
Message = new Regex("Duplicate shape found.+"),
485+
Severity = DiagnosticSeverity.Error,
486+
Locations = new[]
487+
{
488+
new DiagnosticResultLocation("Test0.cs", null, null),
489+
new DiagnosticResultLocation("Test0.cs", null, null),
490+
}
491+
}
492+
).ConfigureAwait(false);
493+
494+
Assert.IsTrue(File.Exists(GeneratedFileName), $"Expected {GeneratedFileName} to be generated when generate_exceptions_file is enabled.");
495+
var content = File.ReadAllText(GeneratedFileName);
496+
Assert.Contains("Bar", content);
497+
}
498+
finally
499+
{
500+
if (File.Exists(GeneratedFileName))
501+
{
502+
File.Delete(GeneratedFileName);
503+
}
504+
}
505+
}
506+
}
395507
}

0 commit comments

Comments
 (0)