|
| 1 | +# Analyzer Catalog - SWEN3.Paperless.RabbitMq |
| 2 | + |
| 3 | +**Philosophy:** "Zero Tolerance for Real Bugs, Zero Care About Style" |
| 4 | +**Strategy:** CA + VSTHRD + MA + RCS (bugs only) - Modern .NET 10/C# 14 analyzer stack |
| 5 | +**Last Updated:** November 20, 2025 |
| 6 | + |
| 7 | +## Core Principle |
| 8 | + |
| 9 | +We care about: |
| 10 | +- Production deadlocks (VSTHRD002, MA0042) |
| 11 | +- Crashes (VSTHRD100, CA2000) |
| 12 | +- Security holes (CA2100, CA3001, CA5350) |
| 13 | +- Resource leaks (CA2000, CA1816) |
| 14 | +- Dead code (CA1852) |
| 15 | + |
| 16 | +We don't care about: |
| 17 | +- Style opinions (CA1711 disabled; MA0053 suggestion in main, none in tests; MA0048 warning in main, none in tests) |
| 18 | +- Formatting (RCS0001-RCS0063 - disabled entirely) |
| 19 | +- Naming conventions (CA1715, CA1716) |
| 20 | +- Whether you use `var` or explicit types (IDE0008) |
| 21 | +- Brace styles, expression bodies, XML docs everywhere |
| 22 | + |
| 23 | +## Selected Analyzers |
| 24 | + |
| 25 | +| Package | Version | Purpose | Justification | |
| 26 | +|---------|---------|---------|---------------| |
| 27 | +| **Built-in .NET Analyzers** | 10.0.x (SDK) | Performance, reliability, security (CA rules) | Maintained by Microsoft, .NET 10 GA quality | |
| 28 | +| **Meziantou.Analyzer** | 2.0.254+ | Modern C# 14, perf, security, culture-sensitive APIs | Best-in-class for modern C#, actively maintained | |
| 29 | +| **Roslynator.Analyzers** | 4.14.1+ | Code simplification, readability hints | Complements Meziantou (refactoring suggestions) | |
| 30 | +| **MS.VisualStudio.Threading.Analyzers** | 17.14.15+ | Async/await correctness, ConfigureAwait, deadlock prevention | Critical for SSE + RabbitMQ async patterns | |
| 31 | + |
| 32 | +## Disabled Style Analyzers (Not Real Bugs) |
| 33 | + |
| 34 | +| Rule ID | Description | Reason | |
| 35 | +|---------|-------------|--------| |
| 36 | +| **CA1711** | Identifiers should not have incorrect suffix | Disabled - `ISseStream` is not `System.IO.Stream`, suffix is intentional | |
| 37 | +| **MA0048** | File name must match type name | Disabled - Style opinion, not a bug | |
| 38 | +| **MA0053** | Make class sealed | Disabled - Premature optimization, style opinion | |
| 39 | +| **RCS0001-RCS0063** | All Roslynator formatting rules | Disabled entirely - Use built-in formatter, avoid IDE lag | |
| 40 | +| **IDE0008** | Use explicit type vs var | Disabled - Style preference, not a bug | |
| 41 | +| **CA1715** | Identifiers should have correct prefix | Disabled - Naming opinion, not a bug | |
| 42 | +| **CA1716** | Identifiers should not match keywords | Disabled - Too noisy, context makes it clear | |
| 43 | + |
| 44 | +## Excluded Analyzers |
| 45 | + |
| 46 | +| Package | Reason for Exclusion | |
| 47 | +|---------|----------------------| |
| 48 | +| **ErrorProne.NET.CoreAnalyzers** | Beta quality (0.8.1-beta.1), overlaps with MA/VSTHRD on async/nullability, adds noise without value | |
| 49 | +| **SonarLint (IDE)** | Legacy rules, conflicts with modern C# 14 syntax (records, file-scoped namespaces), rely on MSBuild analyzers instead | |
| 50 | +| **StyleCop** | Style-focused, conflicts with modern C# idioms, not worth the noise | |
| 51 | + |
| 52 | +--- |
| 53 | + |
| 54 | +## Overlap Resolution |
| 55 | + |
| 56 | +### ConfigureAwait Enforcement |
| 57 | + |
| 58 | +**Decision:** Keep **VSTHRD111 as sole owner** |
| 59 | +**Disabled:** CA2007, MA0004, RCS1090 |
| 60 | + |
| 61 | +| Rule ID | Library | Description | Status | Reason | |
| 62 | +|---------|---------|-------------|--------|--------| |
| 63 | +| **VSTHRD111** | VS.Threading | Use .ConfigureAwait(bool) | ✅ **KEEP** | Sole owner (detects Task.Delay, WhenAll, most comprehensive) | |
| 64 | +| CA2007 | NetAnalyzers | Do not directly await a Task | ❌ DISABLE | Redundant (VSTHRD111 covers it) | |
| 65 | +| MA0004 | Meziantou | Use ConfigureAwait(false) | ❌ DISABLE | Redundant (would double-fire with VSTHRD111) | |
| 66 | +| RCS1090 | Roslynator | Add/remove ConfigureAwait(false) | ❌ DISABLE | Redundant (VSTHRD111 covers it) | |
| 67 | + |
| 68 | +--- |
| 69 | + |
| 70 | +### CancellationToken Propagation |
| 71 | + |
| 72 | +**Decision:** Keep **MA0032/MA0040/MA0079/MA0080 as sole owners** |
| 73 | +**Disabled:** CA2016, RCS1229 |
| 74 | + |
| 75 | +| Rule ID | Library | Description | Status | Reason | |
| 76 | +|---------|---------|-------------|--------|--------| |
| 77 | +| **MA0032** | Meziantou | Use an overload with a CancellationToken | ✅ **KEEP** | Detects missing CT overloads | |
| 78 | +| **MA0040** | Meziantou | Forward the CancellationToken to methods | ✅ **KEEP** | Ensures CT propagation in call chains | |
| 79 | +| **MA0079** | Meziantou | Forward CT using .WithCancellation() | ✅ **KEEP** | IAsyncEnumerable-specific | |
| 80 | +| **MA0080** | Meziantou | Use CT using .WithCancellation() | ✅ **KEEP** | IAsyncEnumerable-specific | |
| 81 | +| CA2016 | NetAnalyzers | Forward the CancellationToken | ❌ DISABLE | Redundant (MA rules more comprehensive) | |
| 82 | +| RCS1229 | Roslynator | Use async/await when necessary | ❌ DISABLE | Redundant (MA rules cover CT usage) | |
| 83 | + |
| 84 | +--- |
| 85 | + |
| 86 | +### Async Void / Naming |
| 87 | + |
| 88 | +**Decision:** Keep **VSTHRD100/VSTHRD101/VSTHRD200** |
| 89 | +**Disabled:** RCS1046/RCS1047 (Roslynator async naming) |
| 90 | + |
| 91 | +| Rule ID | Library | Description | Status | Reason | |
| 92 | +|---------|---------|-------------|--------|--------| |
| 93 | +| **VSTHRD100** | VS.Threading | Avoid async void methods | ✅ **KEEP** | Catches async void outside event handlers | |
| 94 | +| **VSTHRD101** | VS.Threading | Avoid unsupported async delegates | ✅ **KEEP** | Prevents async void in LINQ/Task.Run | |
| 95 | +| **VSTHRD200** | VS.Threading | Use Async naming convention | ✅ **KEEP** | Enforces XxxAsync suffix | |
| 96 | +| RCS1046 | Roslynator | Method name should end with 'Async' | ❌ DISABLE | Redundant (VSTHRD200 covers it) | |
| 97 | +| RCS1047 | Roslynator | Non-async method should not end with 'Async' | ❌ DISABLE | Redundant (VSTHRD200 covers it) | |
| 98 | + |
| 99 | +--- |
| 100 | + |
| 101 | +### Await Before Dispose |
| 102 | + |
| 103 | +**Decision:** Keep **MA0100/MA0129** |
| 104 | +**Disabled:** VSTHRD114 |
| 105 | + |
| 106 | +| Rule ID | Library | Description | Status | Reason | |
| 107 | +|---------|---------|-------------|--------|--------| |
| 108 | +| **MA0100** | Meziantou | Await task before disposing of resources | ✅ **KEEP** | Catches `using (var x = Task.Run(...))` | |
| 109 | +| **MA0129** | Meziantou | Await task in using statement | ✅ **KEEP** | Detects incomplete awaits in using | |
| 110 | +| VSTHRD114 | VS.Threading | Avoid returning null from Task method | ⚠️ KEEP | Different scenario (null Task returns) | |
| 111 | +| EPC31 | ErrorProne | Do not return null for Task-like types | ❌ N/A | Not installed (ErrorProne excluded) | |
| 112 | + |
| 113 | +--- |
| 114 | + |
| 115 | +### String Comparison |
| 116 | + |
| 117 | +**Decision:** Keep **MA0001 as sole owner** |
| 118 | +**Disabled:** CA1307, CA1309, CA1310, RCS1155 |
| 119 | + |
| 120 | +| Rule ID | Library | Description | Status | Reason | |
| 121 | +|---------|---------|-------------|--------|--------| |
| 122 | +| **MA0001** | Meziantou | StringComparison is missing | ✅ **KEEP** | Sole owner (most comprehensive, detects more patterns) | |
| 123 | +| CA1307 | NetAnalyzers | Specify StringComparison for clarity | ❌ DISABLE | Redundant (MA0001 covers it) | |
| 124 | +| CA1309 | NetAnalyzers | Use ordinal StringComparison | ❌ DISABLE | Redundant (MA0001 covers it) | |
| 125 | +| CA1310 | NetAnalyzers | Specify StringComparison for correctness | ❌ DISABLE | Redundant (MA0001 covers it) | |
| 126 | +| RCS1155 | Roslynator | Use StringComparison when comparing strings | ❌ DISABLE | Redundant (MA0001 covers it) | |
| 127 | + |
| 128 | +--- |
| 129 | + |
| 130 | +### LINQ Optimization |
| 131 | + |
| 132 | +**Decision:** Keep **MA0020/MA0031 as sole owners** |
| 133 | +**Disabled:** CA1826, CA1827, RCS1077, RCS1080 |
| 134 | + |
| 135 | +| Rule ID | Library | Description | Status | Reason | |
| 136 | +|---------|---------|-------------|--------|--------| |
| 137 | +| **MA0020** | Meziantou | Use direct methods instead of LINQ | ✅ **KEEP** | Sole owner (List<T>.Find vs FirstOrDefault, etc.) | |
| 138 | +| **MA0031** | Meziantou | Optimize Enumerable.Count() usage | ✅ **KEEP** | Sole owner (.Count property vs .Count() method) | |
| 139 | +| CA1826 | NetAnalyzers | Do not use Enumerable on indexable collections | ❌ DISABLE | Redundant (MA0020 covers it) | |
| 140 | +| CA1827 | NetAnalyzers | Do not use Count() when Any() can be used | ❌ DISABLE | Redundant (MA0031 covers it) | |
| 141 | +| RCS1077 | Roslynator | Optimize LINQ method call | ❌ DISABLE | Redundant (MA0020/MA0031 cover it) | |
| 142 | +| RCS1080 | Roslynator | Use Count/Length instead of Any | ❌ DISABLE | Redundant (MA0031 covers it) | |
| 143 | + |
| 144 | +--- |
| 145 | + |
| 146 | +### StringBuilder Optimization |
| 147 | + |
| 148 | +**Decision:** Keep **MA0028 as sole owner** |
| 149 | +**Disabled:** CA1830, CA1834, RCS1197 |
| 150 | + |
| 151 | +| Rule ID | Library | Description | Status | Reason | |
| 152 | +|---------|---------|-------------|--------|--------| |
| 153 | +| **MA0028** | Meziantou | Optimize StringBuilder usage | ✅ **KEEP** | Sole owner (Append(char) vs Append(string), more patterns) | |
| 154 | +| CA1830 | NetAnalyzers | Prefer StringBuilder.Append(char) | ❌ DISABLE | Redundant (MA0028 covers it) | |
| 155 | +| CA1834 | NetAnalyzers | Use StringBuilder.Append(char) | ❌ DISABLE | Redundant (MA0028 covers it) | |
| 156 | +| RCS1197 | Roslynator | Optimize StringBuilder.Append/AppendLine | ❌ DISABLE | Redundant (MA0028 covers it) | |
| 157 | + |
| 158 | +--- |
| 159 | + |
| 160 | +### Array Allocations |
| 161 | + |
| 162 | +**Decision:** Keep **MA0005** |
| 163 | +**Disabled:** CA1825 |
| 164 | + |
| 165 | +| Rule ID | Library | Description | Status | Reason | |
| 166 | +|---------|---------|-------------|--------|--------| |
| 167 | +| **MA0005** | Meziantou | Use Array.Empty<T>() | ✅ **KEEP** | Modern C# 12+ collection expressions support | |
| 168 | +| CA1825 | NetAnalyzers | Avoid zero-length array allocations | ❌ DISABLE | Redundant (MA0005 covers it) | |
| 169 | + |
| 170 | +--- |
| 171 | + |
| 172 | +### Async Call in Sync Context |
| 173 | + |
| 174 | +**Decision:** Keep **MA0042** |
| 175 | +**Disabled:** CA1849 |
| 176 | + |
| 177 | +| Rule ID | Library | Description | Status | Reason | |
| 178 | +|---------|---------|-------------|--------|--------| |
| 179 | +| **MA0042** | Meziantou | Do not use blocking calls in async method | ✅ **KEEP** | Fewer false positives, supports CreateAsyncScope | |
| 180 | +| CA1849 | NetAnalyzers | Call async methods when in async method | ❌ DISABLE | Redundant (MA0042 covers it) | |
| 181 | + |
| 182 | +--- |
| 183 | + |
| 184 | +### Observe Async Results |
| 185 | + |
| 186 | +**Decision:** Keep **VSTHRD110** + **MA0134** |
| 187 | +**Disabled:** None |
| 188 | + |
| 189 | +| Rule ID | Library | Description | Status | Reason | |
| 190 | +|---------|---------|-------------|--------|--------| |
| 191 | +| **VSTHRD110** | VS.Threading | Observe result of async calls | ✅ **KEEP** | Catches fire-and-forget Task | |
| 192 | +| **MA0134** | Meziantou | Observe result of async calls | ✅ **KEEP** | Complements VSTHRD110 (different patterns) | |
| 193 | +| EPC13 | ErrorProne | Suspiciously unobserved result | ❌ N/A | Not installed (ErrorProne excluded) | |
| 194 | + |
| 195 | +--- |
| 196 | + |
| 197 | +## Roslynator Formatting Rules (All Disabled) |
| 198 | + |
| 199 | +**Decision:** Disable **ALL** Roslynator formatting rules (RCS0001-RCS0063) |
| 200 | +**Reason:** Use built-in .NET formatter only (avoid IDE lag, conflicts with .editorconfig) |
| 201 | + |
| 202 | +| Rule ID Pattern | Count | Status | Reason | |
| 203 | +|----------------|-------|--------|--------| |
| 204 | +| RCS0001-RCS0063 | ~60 rules | ❌ DISABLED | Use .editorconfig + built-in formatter instead | |
| 205 | + |
| 206 | +Examples: |
| 207 | +- RCS0001: Add blank line after embedded statement → **DISABLED** |
| 208 | +- RCS0027: Place new line after/before binary operator → **DISABLED** |
| 209 | +- RCS0046: Use spaces instead of tab → **DISABLED** |
| 210 | + |
| 211 | +--- |
| 212 | + |
| 213 | +## Critical Rules to Keep (High Signal) |
| 214 | + |
| 215 | +### Performance (CA18xx) |
| 216 | + |
| 217 | +| Rule ID | Description | Severity | |
| 218 | +|---------|-------------|----------| |
| 219 | +| CA1848 | Use LoggerMessage delegates | ⚠️ Warning | |
| 220 | +| CA1851 | Possible multiple enumerations | ⚠️ Warning | |
| 221 | +| CA1852 | Seal internal types | ⚠️ Warning | |
| 222 | +| CA1853 | Unnecessary Dictionary.ContainsKey | ⚠️ Warning | |
| 223 | +| CA1854 | Prefer TryGetValue | ⚠️ Warning | |
| 224 | +| CA1860 | Avoid Enumerable.Any() | ⚠️ Warning | |
| 225 | +| CA1861 | Avoid constant arrays as arguments | ⚠️ Warning | |
| 226 | +| CA1869 | Cache JsonSerializerOptions | ⚠️ Warning | |
| 227 | + |
| 228 | +### Reliability (CA20xx) |
| 229 | + |
| 230 | +| Rule ID | Description | Severity | |
| 231 | +|---------|-------------|----------| |
| 232 | +| CA2000 | Dispose objects before losing scope | ⚠️ Warning | |
| 233 | +| CA2008 | Do not create tasks without TaskScheduler | ⚠️ Warning | |
| 234 | +| CA2012 | Use ValueTasks correctly | ⚠️ Warning | |
| 235 | +| CA2013 | Do not use ReferenceEquals with value types | ⚠️ Warning | |
| 236 | +| CA2014 | Do not use stackalloc in loops | ⚠️ Warning | |
| 237 | + |
| 238 | +### Security (CA3xxx) |
| 239 | + |
| 240 | +| Rule ID | Description | Severity | |
| 241 | +|---------|-------------|----------| |
| 242 | +| CA3001 | SQL injection vulnerabilities | ⚠️ Warning | |
| 243 | +| CA3002 | XSS vulnerabilities | ⚠️ Warning | |
| 244 | +| CA3006 | Process command injection | ⚠️ Warning | |
| 245 | +| CA3012 | Regex injection vulnerabilities | ⚠️ Warning | |
| 246 | + |
| 247 | +--- |
| 248 | + |
| 249 | +## Test-Specific Overrides |
| 250 | + |
| 251 | +**File Pattern:** `SWEN3.Paperless.RabbitMq.Tests/**/*.cs` (no leading `**/` in .editorconfig) |
| 252 | + |
| 253 | +| Rule ID | Reason for Disable | |
| 254 | +|---------|-------------------| |
| 255 | +| VSTHRD111 | No SynchronizationContext in xUnit/NUnit | |
| 256 | +| VSTHRD002 | ERROR in prod (sync-over-async = deadlock); disabled in tests | |
| 257 | +| VSTHRD003 | ERROR in prod (awaiting foreign tasks); disabled in tests | |
| 258 | +| MA0004 | ConfigureAwait unnecessary in tests | |
| 259 | +| MA0032 | CT forwarding not needed in tests | |
| 260 | +| MA0040 | CT propagation not critical in tests | |
| 261 | +| MA0134 | Allow fire-and-forget in test setup | |
| 262 | +| MA0002 | String comparers not needed in tests | |
| 263 | +| MA0074 | Avoid `this.` prefix unnecessary in tests | |
| 264 | +| MA0076 | Enum.GetValues<T>() unnecessary in tests | |
| 265 | +| MA0006 | Use String.Equals unnecessary in tests | |
| 266 | +| MA0079 | IAsyncEnumerable CT not needed in tests | |
| 267 | +| IDE0058 | Allow expression-only statements | |
| 268 | +| MA0048 | Allow flexible test file naming | |
| 269 | +| CA1852 | Tests don't need sealing | |
| 270 | +| CA1816 | Tests don't need GC.SuppressFinalize | |
| 271 | +| MA0053 | Tests don't need sealing | |
| 272 | +| CA1707 | Allow underscores in test names | |
| 273 | + |
| 274 | +--- |
| 275 | + |
| 276 | +## Migration Notes |
| 277 | + |
| 278 | +### From No Analyzers → Clean Trinity |
| 279 | + |
| 280 | +1. ✅ **Added:** Directory.Build.props with 3 analyzer packages |
| 281 | +2. ✅ **Added:** .editorconfig with overlap suppressions |
| 282 | +3. ✅ **Disabled:** SonarLint "Sonar way" profile in Rider |
| 283 | +4. ✅ **Excluded:** ErrorProne.NET (beta, overlaps, noise) |
| 284 | + |
| 285 | +### Why No ErrorProne.NET? |
| 286 | + |
| 287 | +| Overlap | ErrorProne Rule | Replaced By | |
| 288 | +|---------|----------------|-------------| |
| 289 | +| ConfigureAwait | EPC14, EPC15 | VSTHRD111 + MA0004 | |
| 290 | +| Async void | EPC27 | VSTHRD100 | |
| 291 | +| Null Task returns | EPC31 | VSTHRD114 | |
| 292 | +| Observe results | EPC13 | VSTHRD110 + MA0134 | |
| 293 | +| Quality | Beta (0.8.1-beta.1) | GA-quality alternatives | |
| 294 | + |
| 295 | +--- |
| 296 | + |
| 297 | +## Maintenance |
| 298 | + |
| 299 | +### Checking for Updates |
| 300 | + |
| 301 | +```bash |
| 302 | +# Check analyzer versions |
| 303 | +dotnet list package --outdated |
| 304 | + |
| 305 | +# Update to latest |
| 306 | +dotnet add package Meziantou.Analyzer |
| 307 | +dotnet add package Roslynator.Analyzers |
| 308 | +dotnet add package Microsoft.VisualStudio.Threading.Analyzers |
| 309 | +``` |
| 310 | + |
| 311 | +### Performance Profiling |
| 312 | + |
| 313 | +```bash |
| 314 | +# Enable analyzer performance reporting |
| 315 | +dotnet build /bl /p:ReportAnalyzer=true |
| 316 | +``` |
| 317 | + |
| 318 | +### Adding New Rules |
| 319 | + |
| 320 | +1. Search for the rule ID in this catalog |
| 321 | +2. If duplicate exists, disable the weaker one |
| 322 | +3. Update .editorconfig with severity |
| 323 | +4. Document the decision in this file |
| 324 | + |
| 325 | +--- |
| 326 | + |
| 327 | +## FAQ |
| 328 | + |
| 329 | +**Q: Why disable MA0004 but keep VSTHRD111?** |
| 330 | +A: VSTHRD111 is the sole owner for ConfigureAwait enforcement. Keeping both would cause double-firing. VSTHRD111 is more comprehensive (catches Task.Delay, WhenAll, etc.). |
| 331 | + |
| 332 | +**Q: Why keep both VSTHRD110 and MA0134?** |
| 333 | +A: They catch different patterns. VSTHRD110 focuses on JoinableTaskFactory, MA0134 catches general fire-and-forget. |
| 334 | + |
| 335 | +**Q: Why disable all Roslynator formatting rules?** |
| 336 | +A: Prevents IDE lag and conflicts. Use .editorconfig + built-in .NET formatter instead. |
| 337 | + |
| 338 | +**Q: When should I add ErrorProne.NET?** |
| 339 | +A: Only if you need stricter nullability/concurrency checks and can tolerate beta-quality diagnostics. Not recommended for this project. |
| 340 | + |
| 341 | +--- |
| 342 | + |
| 343 | +## References |
| 344 | + |
| 345 | +- [Meziantou.Analyzer Rules](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/) |
| 346 | +- [Roslynator Rules](https://github.com/dotnet/roslynator/blob/main/docs/analyzers/README.md) |
| 347 | +- [VS.Threading Rules](https://github.com/microsoft/vs-threading/blob/main/doc/analyzers/index.md) |
| 348 | +- [.NET Code Analysis (CA rules)](https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/) |
0 commit comments