From 504d20357a683fd3f87890e2dcc4d9984f18154b Mon Sep 17 00:00:00 2001 From: Sergey Teplyakov Date: Tue, 1 Jul 2025 13:42:46 -0700 Subject: [PATCH] Annotate rule docs with violation markers and fix EPC20 category MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added explicit ❌ and ✅ markers to code examples in rule documentation files to clarify violations and correct usage. Also changed the category for EPC20 in DiagnosticDescriptors.cs from AsyncCategory to CodeSmellCategory for accuracy. --- docs/Rules/EPC11.md | 6 ++-- docs/Rules/EPC12.md | 6 ++-- docs/Rules/EPC13.md | 12 ++++---- docs/Rules/EPC15.md | 8 ++--- docs/Rules/EPC16.md | 10 +++---- docs/Rules/EPC17.md | 6 ++-- docs/Rules/EPC18.md | 12 ++++---- docs/Rules/EPC19.md | 14 ++++----- docs/Rules/EPC20.md | 8 ++--- docs/Rules/EPC23.md | 8 ++--- docs/Rules/EPC24.md | 10 +++---- docs/Rules/EPC25.md | 8 ++--- docs/Rules/EPC26.md | 10 +++---- docs/Rules/EPC27.md | 8 ++--- docs/Rules/EPC28.md | 10 +++---- docs/Rules/EPC29.md | 12 ++++---- docs/Rules/EPC30.md | 8 ++--- docs/Rules/EPC31.md | 14 ++++----- docs/Rules/EPC32.md | 8 ++--- docs/Rules/EPC33.md | 17 +++++------ docs/Rules/EPC34.md | 10 +++---- docs/Rules/ERP021.md | 8 ++--- docs/Rules/ERP022.md | 16 ++++------ docs/Rules/ERP031.md | 18 +++++------ docs/Rules/ERP041.md | 20 ++++++------- docs/Rules/ERP042.md | 30 +++++++++---------- .../DiagnosticDescriptors.cs | 2 +- 27 files changed, 147 insertions(+), 152 deletions(-) diff --git a/docs/Rules/EPC11.md b/docs/Rules/EPC11.md index 88c217e..28f8e63 100644 --- a/docs/Rules/EPC11.md +++ b/docs/Rules/EPC11.md @@ -14,7 +14,7 @@ public class MyClass public override bool Equals(object obj) { // Suspicious: only using static members or not using 'this' instance - return SomeStaticProperty == 42; + return SomeStaticProperty == 42; // ❌ EPC11 } } ``` @@ -27,7 +27,7 @@ public class Person public override bool Equals(object obj) { // Suspicious: parameter 'obj' is never used - return this.Name == "test"; + return this.Name == "test"; // ❌ EPC11 } } ``` @@ -49,7 +49,7 @@ public class Person { if (obj is Person other) { - return this.Name == other.Name; + return this.Name == other.Name; // ✅ Correct } return false; } diff --git a/docs/Rules/EPC12.md b/docs/Rules/EPC12.md index 8c763bf..1fd288b 100644 --- a/docs/Rules/EPC12.md +++ b/docs/Rules/EPC12.md @@ -17,7 +17,7 @@ try catch (Exception ex) { // Only using ex.Message - this triggers the warning - Console.WriteLine(ex.Message); + Console.WriteLine(ex.Message); // ❌ EPC12 } ``` @@ -34,8 +34,8 @@ try catch (Exception ex) { // Log the full exception object - Console.WriteLine(ex.ToString()); + Console.WriteLine(ex.ToString()); // ✅ Correct // Or access the exception object directly - LogException(ex); + LogException(ex); // ✅ Correct } ``` diff --git a/docs/Rules/EPC13.md b/docs/Rules/EPC13.md index 3b77e3b..a90482a 100644 --- a/docs/Rules/EPC13.md +++ b/docs/Rules/EPC13.md @@ -14,13 +14,13 @@ public class Example public void ProcessData() { // Return value is ignored - this triggers the warning - GetImportantValue(); + GetImportantValue(); // ❌ EPC13 // String methods return new strings but result is ignored - "hello".ToUpper(); + "hello".ToUpper(); // ❌ EPC13 // Collection methods that return new collections - list.Where(x => x > 0); + list.Where(x => x > 0); // ❌ EPC13 } public string GetImportantValue() @@ -40,14 +40,14 @@ public class Example public void ProcessData() { // Store the result in a variable - var importantValue = GetImportantValue(); + var importantValue = GetImportantValue(); // ✅ Correct // Use the result directly - var upperText = "hello".ToUpper(); + var upperText = "hello".ToUpper(); // ✅ Correct Console.WriteLine(upperText); // Chain operations or store result - var filteredList = list.Where(x => x > 0).ToList(); + var filteredList = list.Where(x => x > 0).ToList(); // ✅ Correct } public string GetImportantValue() diff --git a/docs/Rules/EPC15.md b/docs/Rules/EPC15.md index ed1a7e2..41dbdcb 100644 --- a/docs/Rules/EPC15.md +++ b/docs/Rules/EPC15.md @@ -12,10 +12,10 @@ The analyzer warns when `ConfigureAwait(false)` should be used but is missing. T public async Task ProcessAsync() { // Missing ConfigureAwait(false) - this triggers the warning - await SomeAsyncMethod(); + await SomeAsyncMethod(); // ❌ EPC15 // Also missing ConfigureAwait(false) - var result = await GetDataAsync(); + var result = await GetDataAsync(); // ❌ EPC15 } ``` @@ -27,9 +27,9 @@ Add `ConfigureAwait(false)` to all await expressions: public async Task ProcessAsync() { // Add ConfigureAwait(false) to avoid capturing sync context - await SomeAsyncMethod().ConfigureAwait(false); + await SomeAsyncMethod().ConfigureAwait(false); // ✅ Correct - var result = await GetDataAsync().ConfigureAwait(false); + var result = await GetDataAsync().ConfigureAwait(false); // ✅ Correct } ``` diff --git a/docs/Rules/EPC16.md b/docs/Rules/EPC16.md index f947400..c2799cd 100644 --- a/docs/Rules/EPC16.md +++ b/docs/Rules/EPC16.md @@ -14,7 +14,7 @@ public async Task ProcessAsync() SomeService service = GetService(); // might return null // Dangerous: if service is null, this returns null, and awaiting null throws NRE - await service?.ProcessAsync(); + await service?.ProcessAsync(); // ❌ EPC16 } ``` @@ -24,7 +24,7 @@ public async Task GetDataAsync() var client = GetHttpClient(); // might return null // This will throw NRE if client is null - return await client?.GetStringAsync("http://example.com"); + return await client?.GetStringAsync("http://example.com"); // ❌ EPC16 } ``` @@ -40,11 +40,11 @@ public async Task ProcessAsync() // Option 1: Check for null first if (service != null) { - await service.ProcessAsync(); + await service.ProcessAsync(); // ✅ Correct } // Option 2: Use null-coalescing with Task.CompletedTask - await (service?.ProcessAsync() ?? Task.CompletedTask); + await (service?.ProcessAsync() ?? Task.CompletedTask); // ✅ Correct } ``` @@ -59,6 +59,6 @@ public async Task GetDataAsync() return null; // or throw, or return default value } - return await client.GetStringAsync("http://example.com"); + return await client.GetStringAsync("http://example.com"); // ✅ Correct } ``` diff --git a/docs/Rules/EPC17.md b/docs/Rules/EPC17.md index a028a58..fd4fbfc 100644 --- a/docs/Rules/EPC17.md +++ b/docs/Rules/EPC17.md @@ -12,14 +12,14 @@ The analyzer warns against using async void delegates. Unlike async void methods public void SetupHandlers() { // Dangerous: async void delegate - SomeEvent += async () => + SomeEvent += async () => // ❌ EPC17 { await SomeAsyncMethod(); // If this throws, it will crash the app }; // Another dangerous pattern - Task.Run(async () => + Task.Run(async () => // ❌ EPC17 { await ProcessAsync(); // Exceptions here are unhandled @@ -31,7 +31,7 @@ public void SetupHandlers() public void RegisterCallback() { // Async void delegate in callback - RegisterCallback(async () => + RegisterCallback(async () => // ❌ EPC17 { await DoWorkAsync(); }); diff --git a/docs/Rules/EPC18.md b/docs/Rules/EPC18.md index 309c19e..db227c7 100644 --- a/docs/Rules/EPC18.md +++ b/docs/Rules/EPC18.md @@ -17,13 +17,13 @@ public async Task GetDataAsync() public void ProcessData() { // Missing await - Task converted to string - string result = GetDataAsync(); // This will be "System.Threading.Tasks.Task`1[System.String]" + string result = GetDataAsync(); // ❌ EPC18 - This will be "System.Threading.Tasks.Task`1[System.String]" // In string interpolation - Console.WriteLine($"Result: {GetDataAsync()}"); // Prints task type, not result + Console.WriteLine($"Result: {GetDataAsync()}"); // ❌ EPC18 - Prints task type, not result // In concatenation - string message = "Data: " + GetDataAsync(); // Concatenates with task type + string message = "Data: " + GetDataAsync(); // ❌ EPC18 - Concatenates with task type } ``` @@ -40,12 +40,12 @@ public async Task GetDataAsync() public async Task ProcessData() { // Properly await the task - string result = await GetDataAsync(); // Now gets "Hello World" + string result = await GetDataAsync(); // ✅ Correct - Now gets "Hello World" // In string interpolation - Console.WriteLine($"Result: {await GetDataAsync()}"); // Prints actual result + Console.WriteLine($"Result: {await GetDataAsync()}"); // ✅ Correct - Prints actual result // In concatenation - string message = "Data: " + await GetDataAsync(); // Concatenates with actual result + string message = "Data: " + await GetDataAsync(); // ✅ Correct - Concatenates with actual result } ``` \ No newline at end of file diff --git a/docs/Rules/EPC19.md b/docs/Rules/EPC19.md index f97f087..7ebc29b 100644 --- a/docs/Rules/EPC19.md +++ b/docs/Rules/EPC19.md @@ -14,7 +14,7 @@ public class MyService public void StartOperation(CancellationToken cancellationToken) { // Registration is not stored - potential memory leak - cancellationToken.Register(() => + cancellationToken.Register(() => // ❌ EPC19 { Console.WriteLine("Operation cancelled"); }); @@ -26,7 +26,7 @@ public class MyService public async Task ProcessAsync(CancellationToken token) { // Registration result is ignored - token.Register(OnCancellation); + token.Register(OnCancellation); // ❌ EPC19 await DoWorkAsync(); } @@ -49,7 +49,7 @@ public class MyService : IDisposable public void StartOperation(CancellationToken cancellationToken) { // Store the registration - _registration = cancellationToken.Register(() => + _registration = cancellationToken.Register(() => // ✅ Correct { Console.WriteLine("Operation cancelled"); }); @@ -58,7 +58,7 @@ public class MyService : IDisposable public void Dispose() { // Dispose the registration to prevent memory leaks - _registration.Dispose(); + _registration.Dispose(); // ✅ Correct } } ``` @@ -67,21 +67,21 @@ public class MyService : IDisposable public async Task ProcessAsync(CancellationToken token) { // Store registration and dispose in finally block - var registration = token.Register(OnCancellation); + var registration = token.Register(OnCancellation); // ✅ Correct try { await DoWorkAsync(); } finally { - registration.Dispose(); + registration.Dispose(); // ✅ Correct } } // Or use using statement public async Task ProcessAsync(CancellationToken token) { - using var registration = token.Register(OnCancellation); + using var registration = token.Register(OnCancellation); // ✅ Correct await DoWorkAsync(); // registration automatically disposed here } diff --git a/docs/Rules/EPC20.md b/docs/Rules/EPC20.md index d0d0149..f2a906c 100644 --- a/docs/Rules/EPC20.md +++ b/docs/Rules/EPC20.md @@ -21,13 +21,13 @@ public void Example() var person = new Person { Name = "John", Age = 30 }; // Using default ToString() - outputs "Person" instead of useful info - Console.WriteLine(person.ToString()); + Console.WriteLine(person.ToString()); // ❌ EPC20 // Implicit ToString() call - string personString = person; + string personString = person; // ❌ EPC20 // In string interpolation - Console.WriteLine($"Person: {person}"); + Console.WriteLine($"Person: {person}"); // ❌ EPC20 } ``` @@ -42,7 +42,7 @@ public class Person public int Age { get; set; } // Custom ToString() implementation - public override string ToString() + public override string ToString() // ✅ Correct { return $"Person(Name: {Name}, Age: {Age})"; } diff --git a/docs/Rules/EPC23.md b/docs/Rules/EPC23.md index 2d5a125..47ddf14 100644 --- a/docs/Rules/EPC23.md +++ b/docs/Rules/EPC23.md @@ -16,10 +16,10 @@ public void Example() var hashSet = new HashSet { "apple", "banana", "cherry" }; // Inefficient: uses Enumerable.Contains (O(n) linear search) - bool found = hashSet.Contains("apple", StringComparer.OrdinalIgnoreCase); + bool found = hashSet.Contains("apple", StringComparer.OrdinalIgnoreCase); // ❌ EPC23 // Also inefficient when using Enumerable.Contains explicitly - bool found2 = Enumerable.Contains(hashSet, "banana"); + bool found2 = Enumerable.Contains(hashSet, "banana"); // ❌ EPC23 } ``` @@ -33,14 +33,14 @@ public void Example() var hashSet = new HashSet { "apple", "banana", "cherry" }; // Efficient: uses HashSet.Contains (O(1) hash lookup) - bool found = hashSet.Contains("apple"); + bool found = hashSet.Contains("apple"); // ✅ Correct // If you need custom comparison, create HashSet with comparer var caseInsensitiveSet = new HashSet(StringComparer.OrdinalIgnoreCase) { "apple", "banana", "cherry" }; - bool found2 = caseInsensitiveSet.Contains("APPLE"); // O(1) with custom comparer + bool found2 = caseInsensitiveSet.Contains("APPLE"); // ✅ Correct - O(1) with custom comparer } ``` diff --git a/docs/Rules/EPC24.md b/docs/Rules/EPC24.md index be4e5fe..d5f0960 100644 --- a/docs/Rules/EPC24.md +++ b/docs/Rules/EPC24.md @@ -19,13 +19,13 @@ public struct Point public void Example() { // Using struct with default Equals/GetHashCode as dictionary key - var pointData = new Dictionary(); + var pointData = new Dictionary(); // ❌ EPC24 var point = new Point { X = 1, Y = 2 }; pointData[point] = "First point"; // Performance issue! // HashSet also affected - var pointSet = new HashSet(); + var pointSet = new HashSet(); // ❌ EPC24 pointSet.Add(point); // Performance issue! } ``` @@ -41,18 +41,18 @@ public struct Point : IEquatable public int Y { get; set; } // Custom Equals implementation - public bool Equals(Point other) + public bool Equals(Point other) // ✅ Correct { return X == other.X && Y == other.Y; } - public override bool Equals(object obj) + public override bool Equals(object obj) // ✅ Correct { return obj is Point other && Equals(other); } // Custom GetHashCode implementation - public override int GetHashCode() + public override int GetHashCode() // ✅ Correct { return HashCode.Combine(X, Y); } diff --git a/docs/Rules/EPC25.md b/docs/Rules/EPC25.md index 7a6bce6..dede1f4 100644 --- a/docs/Rules/EPC25.md +++ b/docs/Rules/EPC25.md @@ -22,13 +22,13 @@ public void Example() var struct2 = new MyStruct { Value1 = 1, Value2 = 2 }; // Using default ValueType.Equals - slow! - bool areEqual = struct1.Equals(struct2); + bool areEqual = struct1.Equals(struct2); // ❌ EPC25 // Using default ValueType.GetHashCode - slow! - int hash = struct1.GetHashCode(); + int hash = struct1.GetHashCode(); // ❌ EPC25 // In collections this causes performance issues - var dictionary = new Dictionary(); + var dictionary = new Dictionary(); // ❌ EPC25 dictionary[struct1] = "value"; // Triggers slow hash operations } ``` @@ -43,7 +43,7 @@ public struct MyStruct : IEquatable public int Value1 { get; set; } public int Value2 { get; set; } - public bool Equals(MyStruct other) + public bool Equals(MyStruct other) // ✅ Correct { return Value1 == other.Value1 && Value2 == other.Value2; } diff --git a/docs/Rules/EPC26.md b/docs/Rules/EPC26.md index b8847fb..de0ef69 100644 --- a/docs/Rules/EPC26.md +++ b/docs/Rules/EPC26.md @@ -12,20 +12,20 @@ The analyzer warns when Task objects are used in `using` statements or `using` d public async Task Example() { // Bad: using statement with Task - using (var task = SomeAsyncMethod()) + using (var task = SomeAsyncMethod()) // ❌ EPC26 { await task; } // Task.Dispose() called here - problematic! // Also bad: using declaration - using var task2 = GetTaskAsync(); + using var task2 = GetTaskAsync(); // ❌ EPC26 await task2; // task2.Dispose() called at end of scope } public async Task AnotherExample() { // Bad: Task in using block - using var delayTask = Task.Delay(1000); + using var delayTask = Task.Delay(1000); // ❌ EPC26 await delayTask; } ``` @@ -38,10 +38,10 @@ Simply remove the `using` statement and use the task directly: public async Task Example() { // Good: just await the task directly - await SomeAsyncMethod(); + await SomeAsyncMethod(); // ✅ Correct // Or store in variable if needed - var task = GetTaskAsync(); + var task = GetTaskAsync(); // ✅ Correct await task; // No explicit disposal needed } diff --git a/docs/Rules/EPC27.md b/docs/Rules/EPC27.md index d8e33db..6ce1cf8 100644 --- a/docs/Rules/EPC27.md +++ b/docs/Rules/EPC27.md @@ -12,14 +12,14 @@ The analyzer warns when methods are declared as `async void`. These methods are public class Example { // Bad: async void method - public async void ProcessData() + public async void ProcessData() // ❌ EPC27 { await SomeAsyncOperation(); // If this throws, it will crash the app } // Also bad: async void in other contexts - private async void Helper() + private async void Helper() // ❌ EPC27 { await DoWorkAsync(); } @@ -34,14 +34,14 @@ Use `async Task` instead: public class Example { // Good: async Task method - public async Task ProcessDataAsync() + public async Task ProcessDataAsync() // ✅ Correct { await SomeAsyncOperation(); // Exceptions can be caught by caller } // Good: async Task for helper methods - private async Task HelperAsync() + private async Task HelperAsync() // ✅ Correct { await DoWorkAsync(); } diff --git a/docs/Rules/EPC28.md b/docs/Rules/EPC28.md index 293644c..2d75a6f 100644 --- a/docs/Rules/EPC28.md +++ b/docs/Rules/EPC28.md @@ -12,7 +12,7 @@ The analyzer warns when `ExcludeFromCodeCoverageAttribute` is applied to partial using System.Diagnostics.CodeAnalysis; // Bad: ExcludeFromCodeCoverage on partial class -[ExcludeFromCodeCoverage] +[ExcludeFromCodeCoverage] // ❌ EPC28 public partial class MyPartialClass { public void Method1() @@ -33,7 +33,7 @@ public partial class MyPartialClass ```csharp // Also problematic: one part has the attribute -[ExcludeFromCodeCoverage] +[ExcludeFromCodeCoverage] // ❌ EPC28 public partial class DataClass { // Part 1 @@ -52,13 +52,13 @@ Apply the attribute to specific members instead of the partial class: ```csharp public partial class MyPartialClass { - [ExcludeFromCodeCoverage] + [ExcludeFromCodeCoverage] // ✅ Correct public void Method1() { // Explicitly excluded from coverage } - public void Method2() + public void Method2() // ✅ Correct { // Included in coverage } @@ -66,7 +66,7 @@ public partial class MyPartialClass public partial class MyPartialClass { - [ExcludeFromCodeCoverage] + [ExcludeFromCodeCoverage] // ✅ Correct public void Method3() { // Explicitly excluded from coverage diff --git a/docs/Rules/EPC29.md b/docs/Rules/EPC29.md index 8109ace..0948d18 100644 --- a/docs/Rules/EPC29.md +++ b/docs/Rules/EPC29.md @@ -14,14 +14,14 @@ using System.Diagnostics.CodeAnalysis; public class Example { // Bad: No justification provided - [ExcludeFromCodeCoverage] + [ExcludeFromCodeCoverage] // ❌ EPC29 public void SomeMethod() { // Why is this excluded? Unclear to other developers } // Also bad: Empty attribute - [ExcludeFromCodeCoverage()] + [ExcludeFromCodeCoverage()] // ❌ EPC29 public class HelperClass { // No explanation for exclusion @@ -39,25 +39,25 @@ using System.Diagnostics.CodeAnalysis; public class Example { // Good: Clear justification provided - [ExcludeFromCodeCoverage(Justification = "This method is only used for debugging and doesn't need test coverage")] + [ExcludeFromCodeCoverage(Justification = "This method is only used for debugging and doesn't need test coverage")] // ✅ Correct public void DebugHelper() { // Debug-only code } - [ExcludeFromCodeCoverage(Justification = "Generated code that doesn't require testing")] + [ExcludeFromCodeCoverage(Justification = "Generated code that doesn't require testing")] // ✅ Correct public class AutoGeneratedClass { // Auto-generated methods } - [ExcludeFromCodeCoverage(Justification = "Platform-specific code covered by integration tests")] + [ExcludeFromCodeCoverage(Justification = "Platform-specific code covered by integration tests")] // ✅ Correct public void PlatformSpecificMethod() { // Platform-specific implementation } - [ExcludeFromCodeCoverage(Justification = "Simple property with no logic to test")] + [ExcludeFromCodeCoverage(Justification = "Simple property with no logic to test")] // ✅ Correct public string SimpleProperty { get; set; } } ``` diff --git a/docs/Rules/EPC30.md b/docs/Rules/EPC30.md index 4732b43..832b3ef 100644 --- a/docs/Rules/EPC30.md +++ b/docs/Rules/EPC30.md @@ -15,9 +15,9 @@ public class Example public void ProcessData() { // Some processing... - ProcessData(); // Calls itself without obvious base case + ProcessData(); // ❌ EPC30 - Calls itself without obvious base case } - +} ``` ## How to fix @@ -28,7 +28,7 @@ Add proper base cases and ensure recursion terminates: public class Example { // Good: proper recursive method with base case - public int Factorial(int n) + public int Factorial(int n) // ✅ Correct { if (n <= 1) // Base case return 1; @@ -36,7 +36,7 @@ public class Example } // Good: recursive tree traversal with termination condition - public void TraverseTree(TreeNode node) + public void TraverseTree(TreeNode node) // ✅ Correct { if (node == null) // Base case return; diff --git a/docs/Rules/EPC31.md b/docs/Rules/EPC31.md index 6636f00..9ef220c 100644 --- a/docs/Rules/EPC31.md +++ b/docs/Rules/EPC31.md @@ -15,7 +15,7 @@ public class Example public Task ProcessAsync() { if (someCondition) - return null; // This will cause NRE when awaited + return null; // ❌ EPC31 - This will cause NRE when awaited return DoWorkAsync(); } @@ -24,7 +24,7 @@ public class Example public Task GetDataAsync() { if (noData) - return null; // NRE when awaited + return null; // ❌ EPC31 - NRE when awaited return FetchDataAsync(); } @@ -32,7 +32,7 @@ public class Example // Bad: conditional return with null public Task? MaybeProcessAsync() { - return condition ? ProcessDataAsync() : null; + return condition ? ProcessDataAsync() : null; // ❌ EPC31 } } ``` @@ -48,7 +48,7 @@ public class Example public Task ProcessAsync() { if (someCondition) - return Task.CompletedTask; // Safe to await + return Task.CompletedTask; // ✅ Correct - Safe to await return DoWorkAsync(); } @@ -57,7 +57,7 @@ public class Example public Task GetDataAsync() { if (noData) - return Task.FromResult(null); // Or return default value + return Task.FromResult(null); // ✅ Correct - Or return default value return FetchDataAsync(); } @@ -66,7 +66,7 @@ public class Example public Task GetCountAsync() { if (isEmpty) - return Task.FromResult(0); // Return default value + return Task.FromResult(0); // ✅ Correct - Return default value return CalculateCountAsync(); } @@ -82,7 +82,7 @@ public class Example public Task ProcessIfNeededAsync() { if (!needsProcessing) - return Task.CompletedTask; + return Task.CompletedTask; // ✅ Correct return ActualProcessingAsync(); } diff --git a/docs/Rules/EPC32.md b/docs/Rules/EPC32.md index 7acaa66..d3da887 100644 --- a/docs/Rules/EPC32.md +++ b/docs/Rules/EPC32.md @@ -12,7 +12,7 @@ The analyzer warns when `TaskCompletionSource` instances are created without usi public class Example { // Bad: TaskCompletionSource without RunContinuationsAsynchronously - private TaskCompletionSource tcs = new TaskCompletionSource(); + private TaskCompletionSource tcs = new TaskCompletionSource(); // ❌ EPC32 public Task GetResultAsync() { @@ -33,7 +33,7 @@ public class Worker // Also problematic: creating without the flag public Task ProcessAsync() { - var completionSource = new TaskCompletionSource(); + var completionSource = new TaskCompletionSource(); // ❌ EPC32 ThreadPool.QueueUserWorkItem(_ => { @@ -54,7 +54,7 @@ Always use `TaskCreationOptions.RunContinuationsAsynchronously`: public class Example { // Good: TaskCompletionSource with RunContinuationsAsynchronously - private TaskCompletionSource tcs = new TaskCompletionSource( + private TaskCompletionSource tcs = new TaskCompletionSource( // ✅ Correct TaskCreationOptions.RunContinuationsAsynchronously); public Task GetResultAsync() @@ -76,7 +76,7 @@ public class Worker // Good: creating with the flag public Task ProcessAsync() { - var completionSource = new TaskCompletionSource( + var completionSource = new TaskCompletionSource( // ✅ Correct TaskCreationOptions.RunContinuationsAsynchronously); ThreadPool.QueueUserWorkItem(_ => diff --git a/docs/Rules/EPC33.md b/docs/Rules/EPC33.md index edb8dc3..ae93246 100644 --- a/docs/Rules/EPC33.md +++ b/docs/Rules/EPC33.md @@ -12,22 +12,21 @@ The analyzer warns when `Thread.Sleep` is used in async methods. This blocks the public async Task ProcessAsync() { // Bad: Thread.Sleep blocks the thread - Thread.Sleep(1000); // Blocks for 1 second + Thread.Sleep(1000); // ❌ EPC33 - Blocks for 1 second await DoWorkAsync(); // Also bad: Thread.Sleep with TimeSpan - Thread.Sleep(TimeSpan.FromSeconds(2)); + Thread.Sleep(TimeSpan.FromSeconds(2)); // ❌ EPC33 } public async void HandleEventAsync() { // Bad: blocking in async event handler - Thread.Sleep(500); + Thread.Sleep(500); // ❌ EPC33 await UpdateUIAsync(); } - -public async Task GetDataAsync() +``` { // Bad: mixed blocking and async code Thread.Sleep(100); // Simulate delay - wrong way @@ -43,25 +42,25 @@ Use `Task.Delay` with await instead: public async Task ProcessAsync() { // Good: Task.Delay doesn't block the thread - await Task.Delay(1000); // Non-blocking delay + await Task.Delay(1000); // ✅ Correct - Non-blocking delay await DoWorkAsync(); // Good: Task.Delay with TimeSpan - await Task.Delay(TimeSpan.FromSeconds(2)); + await Task.Delay(TimeSpan.FromSeconds(2)); // ✅ Correct } public async void HandleEventAsync() { // Good: non-blocking delay in async event handler - await Task.Delay(500); + await Task.Delay(500); // ✅ Correct await UpdateUIAsync(); } public async Task GetDataAsync() { // Good: all async operations - await Task.Delay(100); // Non-blocking simulation + await Task.Delay(100); // ✅ Correct - Non-blocking simulation return await FetchDataAsync(); } ``` diff --git a/docs/Rules/EPC34.md b/docs/Rules/EPC34.md index 714fc3f..ce7aaab 100644 --- a/docs/Rules/EPC34.md +++ b/docs/Rules/EPC34.md @@ -34,10 +34,10 @@ public class Example var validator = new ValidationService(); // Bad: ignoring return value of method marked with MustUseResult - validator.ValidateInput(input); + validator.ValidateInput(input); // ❌ EPC34 // Also bad: not using the validation result - validator.CheckData(data); + validator.CheckData(data); // ❌ EPC34 // Proceeding without checking validation results - dangerous! DoSomethingWithData(input, data); @@ -57,14 +57,14 @@ public class Example var validator = new ValidationService(); // Good: checking the validation result - bool isValid = validator.ValidateInput(input); + bool isValid = validator.ValidateInput(input); // ✅ Correct if (!isValid) { throw new ArgumentException("Invalid input"); } // Good: using the validation result - var result = validator.CheckData(data); + var result = validator.CheckData(data); // ✅ Correct if (!result.IsValid) { throw new ArgumentException(result.ErrorMessage); @@ -86,7 +86,7 @@ public class Example var validator = new ValidationService(); // Good: using result in conditional - if (validator.ValidateInput(input)) + if (validator.ValidateInput(input)) // ✅ Correct { ProcessValidInput(input); } diff --git a/docs/Rules/ERP021.md b/docs/Rules/ERP021.md index 1e802d2..75983f4 100644 --- a/docs/Rules/ERP021.md +++ b/docs/Rules/ERP021.md @@ -20,7 +20,7 @@ public void ProcessData() LogError(ex); // Bad: throws the exception object, losing original stack trace - throw ex; + throw ex; // ❌ ERP021 } } @@ -33,7 +33,7 @@ public async Task ProcessAsync() catch (InvalidOperationException ex) { // Bad: re-throwing with lost stack trace - throw ex; + throw ex; // ❌ ERP021 } } ``` @@ -54,7 +54,7 @@ public void ProcessData() LogError(ex); // Good: preserves original stack trace - throw; + throw; // ✅ Correct } } @@ -69,7 +69,7 @@ public async Task ProcessAsync() LogError(ex); // Good: original stack trace preserved - throw; + throw; // ✅ Correct } } ``` diff --git a/docs/Rules/ERP022.md b/docs/Rules/ERP022.md index 11a1b18..368b5c1 100644 --- a/docs/Rules/ERP022.md +++ b/docs/Rules/ERP022.md @@ -15,7 +15,7 @@ public void ProcessData() { RiskyOperation(); } - catch (Exception) + catch (Exception) // ❌ ERP022 { // Bad: exception is completely swallowed return; @@ -31,7 +31,7 @@ public void ProcessItems() ProcessItem(item); } } - catch + catch // ❌ ERP022 { // Bad: bare catch that swallows everything // No way to know what went wrong @@ -45,7 +45,7 @@ public bool TryProcess() DoComplexOperation(); return true; } - catch (Exception) + catch (Exception) // ❌ ERP022 { // Bad: swallowing exception and returning false // Caller has no idea what failed @@ -65,7 +65,7 @@ public void ProcessData() { RiskyOperation(); } - catch (Exception ex) + catch (Exception ex) // ✅ Correct { // Good: log the exception before handling _logger.LogError(ex, "Failed to process data"); @@ -84,7 +84,7 @@ public void ProcessItems() ProcessItem(item); } } - catch (Exception ex) + catch (Exception ex) // ✅ Correct { // Good: observe the exception and provide context _logger.LogError(ex, "Failed to process items"); @@ -93,11 +93,7 @@ public void ProcessItems() throw new ProcessingException("Batch processing failed", ex); } } - -public bool TryProcess() -{ - try - { +``` DoComplexOperation(); return true; } diff --git a/docs/Rules/ERP031.md b/docs/Rules/ERP031.md index ef37ef7..9221c61 100644 --- a/docs/Rules/ERP031.md +++ b/docs/Rules/ERP031.md @@ -16,13 +16,13 @@ public class Example public void AddItem(string item) { // Bad: List is not thread-safe for concurrent modifications - _items.Add(item); // Race condition possible + _items.Add(item); // ❌ ERP031 - Race condition possible } public void ProcessItems() { // Bad: Iteration while other threads might modify - foreach (var item in _items) // Potential exception + foreach (var item in _items) // ❌ ERP031 - Potential exception { ProcessItem(item); } @@ -36,12 +36,12 @@ public class SharedCounter public void Increment() { // Bad: Non-atomic read-modify-write operation - _count++; // Race condition + _count++; // ❌ ERP031 - Race condition } public int GetCount() { - return _count; // May return inconsistent value + return _count; // ❌ ERP031 - May return inconsistent value } } ``` @@ -59,13 +59,13 @@ public class ThreadSafeExample public void AddItem(string item) { // Good: ConcurrentBag is thread-safe - _items.Add(item); + _items.Add(item); // ✅ Correct } public void ProcessItems() { // Good: ConcurrentBag supports safe enumeration - foreach (var item in _items) + foreach (var item in _items) // ✅ Correct { ProcessItem(item); } @@ -79,13 +79,13 @@ public class ThreadSafeCounter public void Increment() { // Good: Atomic increment operation - Interlocked.Increment(ref _count); + Interlocked.Increment(ref _count); // ✅ Correct } public int GetCount() { // Good: Atomic read - return Interlocked.Read(ref _count); + return Interlocked.Read(ref _count); // ✅ Correct } } ``` @@ -100,7 +100,7 @@ public class SynchronizedExample public void AddItem(string item) { - lock (_lock) + lock (_lock) // ✅ Correct { _items.Add(item); } diff --git a/docs/Rules/ERP041.md b/docs/Rules/ERP041.md index 5fd18ae..2ad6ed8 100644 --- a/docs/Rules/ERP041.md +++ b/docs/Rules/ERP041.md @@ -13,7 +13,7 @@ using System.Diagnostics.Tracing; // Bad: EventSource class is not sealed [EventSource(Name = "MyCompany-MyProduct-MyEventSource")] -public class MyEventSource : EventSource +public class MyEventSource : EventSource // ❌ ERP041 { public static readonly MyEventSource Log = new MyEventSource(); @@ -25,7 +25,7 @@ public class MyEventSource : EventSource } // Also bad: inheriting from unsealed EventSource -public class ExtendedEventSource : MyEventSource +public class ExtendedEventSource : MyEventSource // ❌ ERP041 { [Event(2, Level = EventLevel.Warning)] public void CustomEvent(string message) @@ -44,26 +44,26 @@ using System.Diagnostics.Tracing; // Good: EventSource class is sealed [EventSource(Name = "MyCompany-MyProduct-MyEventSource")] -public sealed class MyEventSource : EventSource +public sealed class MyEventSource : EventSource // ✅ Correct { public static readonly MyEventSource Log = new MyEventSource(); - private MyEventSource() { } // Private constructor for singleton pattern + private MyEventSource() { } // ✅ Correct - Private constructor for singleton pattern [Event(1, Level = EventLevel.Informational)] - public void ApplicationStarted(string applicationName) + public void ApplicationStarted(string applicationName) // ✅ Correct { WriteEvent(1, applicationName); } [Event(2, Level = EventLevel.Warning)] - public void WarningOccurred(string message) + public void WarningOccurred(string message) // ✅ Correct { WriteEvent(2, message); } [Event(3, Level = EventLevel.Error)] - public void ErrorOccurred(string error, string details) + public void ErrorOccurred(string error, string details) // ✅ Correct { WriteEvent(3, error, details); } @@ -76,13 +76,13 @@ Proper EventSource pattern with all recommendations: using System.Diagnostics.Tracing; [EventSource(Name = "MyCompany-MyProduct-Logging")] -public sealed class ApplicationEventSource : EventSource +public sealed class ApplicationEventSource : EventSource // ✅ Correct { // Singleton instance - public static readonly ApplicationEventSource Log = new ApplicationEventSource(); + public static readonly ApplicationEventSource Log = new ApplicationEventSource(); // ✅ Correct // Private constructor to enforce singleton pattern - private ApplicationEventSource() { } + private ApplicationEventSource() { } // ✅ Correct // Event methods with proper attributes [Event(1, diff --git a/docs/Rules/ERP042.md b/docs/Rules/ERP042.md index f58c6ce..4adcadf 100644 --- a/docs/Rules/ERP042.md +++ b/docs/Rules/ERP042.md @@ -18,30 +18,30 @@ public sealed class ProblematicEventSource : EventSource // Bad: Duplicate event IDs [Event(1)] - public void Event1() => WriteEvent(1); + public void Event1() => WriteEvent(1); // ❌ ERP042 - [Event(1)] // Same ID as above! + [Event(1)] // ❌ ERP042 - Same ID as above! public void AnotherEvent() => WriteEvent(1); // Bad: Event ID mismatch [Event(2)] - public void Event2(string message) => WriteEvent(3, message); // Wrong ID! + public void Event2(string message) => WriteEvent(3, message); // ❌ ERP042 - Wrong ID! // Bad: Parameter count mismatch [Event(3)] - public void Event3(string msg, int count) => WriteEvent(3, msg); // Missing parameter! + public void Event3(string msg, int count) => WriteEvent(3, msg); // ❌ ERP042 - Missing parameter! // Bad: Parameter order mismatch [Event(4)] - public void Event4(string first, int second) => WriteEvent(4, second, first); // Wrong order! + public void Event4(string first, int second) => WriteEvent(4, second, first); // ❌ ERP042 - Wrong order! // Bad: Unsupported parameter type [Event(5)] - public void Event5(DateTime timestamp) => WriteEvent(5, timestamp); // DateTime not supported! + public void Event5(DateTime timestamp) => WriteEvent(5, timestamp); // ❌ ERP042 - DateTime not supported! // Bad: Missing parameter name in WriteEventCore [Event(6)] - public void Event6(string message, int value) + public void Event6(string message, int value) // ❌ ERP042 { WriteEventCore(6, 1, new EventData[] { @@ -67,37 +67,37 @@ public sealed class CorrectEventSource : EventSource // Good: Unique event IDs [Event(1, Level = EventLevel.Informational)] - public void ApplicationStarted(string applicationName, string version) + public void ApplicationStarted(string applicationName, string version) // ✅ Correct { if (IsEnabled()) { - WriteEvent(1, applicationName, version); // Matching ID and parameters + WriteEvent(1, applicationName, version); // ✅ Correct - Matching ID and parameters } } // Good: Correct event ID and parameter mapping [Event(2, Level = EventLevel.Warning)] - public void WarningOccurred(string component, string message, int errorCode) + public void WarningOccurred(string component, string message, int errorCode) // ✅ Correct { if (IsEnabled(EventLevel.Warning)) { - WriteEvent(2, component, message, errorCode); // All parameters included + WriteEvent(2, component, message, errorCode); // ✅ Correct - All parameters included } } // Good: Supported parameter types only [Event(3, Level = EventLevel.Error)] - public void ErrorOccurred(string operation, int errorCode, long timestamp) + public void ErrorOccurred(string operation, int errorCode, long timestamp) // ✅ Correct { if (IsEnabled(EventLevel.Error)) { - WriteEvent(3, operation, errorCode, timestamp); + WriteEvent(3, operation, errorCode, timestamp); // ✅ Correct } } // Good: Proper WriteEventCore usage [Event(4, Level = EventLevel.Verbose)] - public void DetailedEvent(string category, string details) + public void DetailedEvent(string category, string details) // ✅ Correct { if (IsEnabled(EventLevel.Verbose)) { @@ -106,7 +106,7 @@ public sealed class CorrectEventSource : EventSource fixed (char* categoryPtr = category) fixed (char* detailsPtr = details) { - WriteEventCore(4, 2, new EventData[] + WriteEventCore(4, 2, new EventData[] // ✅ Correct { new EventData { diff --git a/src/ErrorProne.NET.CoreAnalyzers/DiagnosticDescriptors.cs b/src/ErrorProne.NET.CoreAnalyzers/DiagnosticDescriptors.cs index 0a29107..a48bd20 100644 --- a/src/ErrorProne.NET.CoreAnalyzers/DiagnosticDescriptors.cs +++ b/src/ErrorProne.NET.CoreAnalyzers/DiagnosticDescriptors.cs @@ -106,7 +106,7 @@ internal static class DiagnosticDescriptors nameof(EPC20), title: "Avoid using default ToString implementation", messageFormat: "A default ToString implementation is used for type {0}", - AsyncCategory, DiagnosticSeverity.Warning, isEnabledByDefault: true, + CodeSmellCategory, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: "A default ToString implementation is rarely gives the result you need.", helpLinkUri: GetHelpUri(nameof(EPC20)));