{"id":32664092,"url":"https://github.com/wieslawsoltes/throwsanalyzer","last_synced_at":"2025-10-31T23:02:54.656Z","repository":{"id":321037781,"uuid":"1082750678","full_name":"wieslawsoltes/ThrowsAnalyzer","owner":"wieslawsoltes","description":"A Roslyn-based C# analyzer that detects exception handling patterns in your code","archived":false,"fork":false,"pushed_at":"2025-10-27T13:19:39.000Z","size":307,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-10-27T13:27:21.670Z","etag":null,"topics":["csharp","dotnet","exception-reporting","exceptions","roslyn","roslyn-analyzer","roslyn-analyzers"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wieslawsoltes.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-10-24T17:58:28.000Z","updated_at":"2025-10-27T13:19:44.000Z","dependencies_parsed_at":null,"dependency_job_id":"ecb157c3-77f0-4e48-92c1-6e571b43f157","html_url":"https://github.com/wieslawsoltes/ThrowsAnalyzer","commit_stats":null,"previous_names":["wieslawsoltes/throwsanalyzer"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/wieslawsoltes/ThrowsAnalyzer","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wieslawsoltes%2FThrowsAnalyzer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wieslawsoltes%2FThrowsAnalyzer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wieslawsoltes%2FThrowsAnalyzer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wieslawsoltes%2FThrowsAnalyzer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wieslawsoltes","download_url":"https://codeload.github.com/wieslawsoltes/ThrowsAnalyzer/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wieslawsoltes%2FThrowsAnalyzer/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":282070789,"owners_count":26608933,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-31T02:00:07.401Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["csharp","dotnet","exception-reporting","exceptions","roslyn","roslyn-analyzer","roslyn-analyzers"],"created_at":"2025-10-31T23:01:20.014Z","updated_at":"2025-10-31T23:02:54.651Z","avatar_url":"https://github.com/wieslawsoltes.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ThrowsAnalyzer\n\nA Roslyn-based C# analyzer that detects exception handling patterns in your code. ThrowsAnalyzer helps identify throw statements, unhandled exceptions, and try-catch blocks across all executable member types.\n\n## Build Status \u0026 NuGet Packages\n\n| Package | Version | Downloads | Build |\n|---------|---------|-----------|-------|\n| **ThrowsAnalyzer** | [![NuGet](https://img.shields.io/nuget/v/ThrowsAnalyzer.svg)](https://www.nuget.org/packages/ThrowsAnalyzer) | [![NuGet Downloads](https://img.shields.io/nuget/dt/ThrowsAnalyzer.svg)](https://www.nuget.org/packages/ThrowsAnalyzer) | [![CI](https://github.com/wieslawsoltes/ThrowsAnalyzer/actions/workflows/ci.yml/badge.svg)](https://github.com/wieslawsoltes/ThrowsAnalyzer/actions/workflows/ci.yml) |\n| **ThrowsAnalyzer.Cli** | [![NuGet](https://img.shields.io/nuget/v/ThrowsAnalyzer.Cli.svg)](https://www.nuget.org/packages/ThrowsAnalyzer.Cli) | [![NuGet Downloads](https://img.shields.io/nuget/dt/ThrowsAnalyzer.Cli.svg)](https://www.nuget.org/packages/ThrowsAnalyzer.Cli) | [![CI](https://github.com/wieslawsoltes/ThrowsAnalyzer/actions/workflows/ci.yml/badge.svg)](https://github.com/wieslawsoltes/ThrowsAnalyzer/actions/workflows/ci.yml) |\n| **RoslynAnalyzer.Core** | [![NuGet](https://img.shields.io/nuget/v/RoslynAnalyzer.Core.svg)](https://www.nuget.org/packages/RoslynAnalyzer.Core) | [![NuGet Downloads](https://img.shields.io/nuget/dt/RoslynAnalyzer.Core.svg)](https://www.nuget.org/packages/RoslynAnalyzer.Core) | [![CI](https://github.com/wieslawsoltes/ThrowsAnalyzer/actions/workflows/ci.yml/badge.svg)](https://github.com/wieslawsoltes/ThrowsAnalyzer/actions/workflows/ci.yml) |\n\n[![License](https://img.shields.io/github/license/wieslawsoltes/ThrowsAnalyzer.svg)](LICENSE)\n\n## Repository Contents\n\nThis repository includes three packages:\n\n- **[ThrowsAnalyzer](#throwsanalyzer-1)** - Comprehensive exception analysis with 30 diagnostics and 16 code fixes\n- **[ThrowsAnalyzer.Cli](#cli-tool)** - Command-line tool for analyzing projects and generating reports\n- **[RoslynAnalyzer.Core](#roslynanalyzercore)** - Reusable infrastructure for building custom Roslyn analyzers\n\n## Features\n\nThrowsAnalyzer provides **30 diagnostic rules** organized into 6 categories, with **16 automated code fixes** for quick issue resolution.\n\n### Diagnostic Rules Summary\n\n| Category | Diagnostics | Description |\n|----------|------------|-------------|\n| **Basic Exception Handling** | THROWS001-003, 004, 007-010 | Fundamental exception patterns and anti-patterns |\n| **Exception Flow Analysis** | THROWS017-019 | Method call exception propagation and documentation |\n| **Async Exception Patterns** | THROWS020-022 | Async/await exception handling issues |\n| **Iterator Exception Patterns** | THROWS023-024 | Exception handling in yield-based iterators |\n| **Lambda Exception Patterns** | THROWS025-026 | Exception handling in lambda expressions |\n| **Best Practices** | THROWS027-030 | Design patterns and performance recommendations |\n\n### Code Fixes Summary\n\n| Code Fix | Diagnostics | Actions |\n|----------|-------------|---------|\n| **Wrap in try-catch** | THROWS001, THROWS002 | Adds try-catch around throwing code |\n| **Fix rethrow** | THROWS004 | Converts `throw ex;` to `throw;` |\n| **Reorder catches** | THROWS007 | Reorders catch clauses from specific to general |\n| **Add/Remove logging** | THROWS008, THROWS003 | Adds logging or removes empty catch |\n| **Remove rethrow-only catch** | THROWS009 | Removes unnecessary catch blocks |\n| **Add exception filter** | THROWS010 | Adds `when` clause to specific catches |\n| **Convert async void** | THROWS021 | Converts async void to async Task |\n| **Add Task observation** | THROWS022 | Adds await or continuation |\n| **Wrap iterator validation** | THROWS023 | Moves validation outside iterator |\n| **Add try-finally** | THROWS024 | Adds try-finally for cleanup |\n| **Wrap lambda in try-catch** | THROWS025, THROWS026 | Adds exception handling to lambdas |\n| **Refactor control flow** | THROWS027 | Suggests return value instead of exceptions |\n| **Rename exception** | THROWS028 | Renames to follow convention |\n| **Move to cold path** | THROWS029 | Suggests refactoring for performance |\n| **Add XML docs** | THROWS019 | Documents thrown exceptions |\n| **Suggest Result pattern** | THROWS030 | Suggests Result\u003cT\u003e for error handling |\n\n## Supported Member Types\n\nThrowsAnalyzer analyzes exception handling patterns in:\n\n- Methods\n- Constructors and Destructors\n- Properties (including expression-bodied properties)\n- Property Accessors (get, set, init, add, remove)\n- Operators (binary, unary, conversion)\n- Local Functions\n- Lambda Expressions (simple and parenthesized)\n- Anonymous Methods\n\n## Installation\n\n### Analyzer Library\n\nAdd the analyzer to your project via NuGet:\n\n```bash\ndotnet add package ThrowsAnalyzer\n```\n\nOnce installed, the analyzer runs automatically during compilation. Diagnostics will appear in your IDE and build output.\n\n### CLI Tool\n\nInstall the command-line tool globally to analyze projects and generate reports:\n\n```bash\ndotnet tool install --global ThrowsAnalyzer.Cli\n```\n\n#### CLI Quick Start\n\n```bash\n# Analyze a project and generate reports\nthrows-analyzer analyze MyProject.csproj\n\n# Analyze a solution\nthrows-analyzer analyze MySolution.sln\n\n# Generate HTML and Markdown reports\nthrows-analyzer analyze MyProject.csproj --verbose --open\n```\n\nThe CLI tool generates comprehensive reports showing:\n- Summary statistics by diagnostic ID, project, severity, and file\n- Interactive HTML reports with sortable tables\n- Markdown reports for documentation\n- Detailed diagnostics with code snippets\n\nSee [CLI Tool Documentation](docs/guides/CLI_TOOL.md) for complete usage guide and CI/CD integration examples.\n\n## Configuration\n\nThrowsAnalyzer provides granular configuration options through `.editorconfig` files. You can control analyzer enablement, severity, and which member types to analyze.\n\n### Enabling/Disabling Individual Analyzers\n\nControl whether each analyzer is completely enabled or disabled:\n\n```ini\n[*.cs]\n\n# Enable/disable throw statement analyzer (THROWS001)\nthrows_analyzer_enable_throw_statement = true\n\n# Enable/disable unhandled throw analyzer (THROWS002)\nthrows_analyzer_enable_unhandled_throw = true\n\n# Enable/disable try-catch block analyzer (THROWS003)\nthrows_analyzer_enable_try_catch = true\n```\n\nAll analyzers are enabled by default. Setting an option to `false` completely disables that analyzer, regardless of severity settings.\n\n### Configuring Analyzer Severity\n\nControl the severity of each diagnostic rule:\n\n```ini\n[*.cs]\n\n# Basic analyzers\n# THROWS001: Detects throw statements in members\ndotnet_diagnostic.THROWS001.severity = suggestion\n\n# THROWS002: Detects unhandled throw statements (not wrapped in try-catch)\ndotnet_diagnostic.THROWS002.severity = warning\n\n# THROWS003: Detects try-catch blocks in members\ndotnet_diagnostic.THROWS003.severity = suggestion\n\n# Advanced type-aware analyzers\n# THROWS004: Rethrow anti-pattern (throw ex; instead of throw;)\ndotnet_diagnostic.THROWS004.severity = warning\n\n# THROWS007: Unreachable catch clause due to ordering\ndotnet_diagnostic.THROWS007.severity = warning\n\n# THROWS008: Empty catch block swallows exceptions\ndotnet_diagnostic.THROWS008.severity = warning\n\n# THROWS009: Catch block only rethrows exception\ndotnet_diagnostic.THROWS009.severity = suggestion\n\n# THROWS010: Overly broad exception catch\ndotnet_diagnostic.THROWS010.severity = suggestion\n```\n\n**Severity options:** `none`, `silent`, `suggestion`, `warning`, `error`\n\n### Configuring Member Type Analysis\n\nSelectively enable or disable analysis for specific member types:\n\n```ini\n[*.cs]\n\n# Analyze regular methods\nthrows_analyzer_analyze_methods = true\n\n# Analyze constructors\nthrows_analyzer_analyze_constructors = true\n\n# Analyze destructors/finalizers\nthrows_analyzer_analyze_destructors = true\n\n# Analyze operator overloads\nthrows_analyzer_analyze_operators = true\n\n# Analyze conversion operators (implicit/explicit)\nthrows_analyzer_analyze_conversion_operators = true\n\n# Analyze properties (expression-bodied properties)\nthrows_analyzer_analyze_properties = true\n\n# Analyze property accessors (get, set, init, add, remove)\nthrows_analyzer_analyze_accessors = true\n\n# Analyze local functions\nthrows_analyzer_analyze_local_functions = true\n\n# Analyze lambda expressions\nthrows_analyzer_analyze_lambdas = true\n\n# Analyze anonymous methods (delegate { } syntax)\nthrows_analyzer_analyze_anonymous_methods = true\n```\n\nAll member types are analyzed by default. Set any option to `false` to disable analysis for that member type.\n\n### Example Configurations\n\n#### Minimal Configuration (Methods and Constructors Only)\n\n```ini\n[*.cs]\nthrows_analyzer_analyze_methods = true\nthrows_analyzer_analyze_constructors = true\nthrows_analyzer_analyze_destructors = false\nthrows_analyzer_analyze_operators = false\nthrows_analyzer_analyze_conversion_operators = false\nthrows_analyzer_analyze_properties = false\nthrows_analyzer_analyze_accessors = false\nthrows_analyzer_analyze_local_functions = false\nthrows_analyzer_analyze_lambdas = false\nthrows_analyzer_analyze_anonymous_methods = false\n```\n\n#### Focus on Unhandled Exceptions Only\n\n```ini\n[*.cs]\nthrows_analyzer_enable_throw_statement = false\nthrows_analyzer_enable_unhandled_throw = true\nthrows_analyzer_enable_try_catch = false\ndotnet_diagnostic.THROWS002.severity = error\n```\n\n#### Disable Analysis for Lambdas and Local Functions\n\n```ini\n[*.cs]\nthrows_analyzer_analyze_local_functions = false\nthrows_analyzer_analyze_lambdas = false\nthrows_analyzer_analyze_anonymous_methods = false\n```\n\nSee [.editorconfig.example](.editorconfig.example) for a complete configuration template.\n\n## Complete Diagnostic Reference\n\n### Category 1: Basic Exception Handling (8 rules)\n\n#### THROWS001: Method contains throw statement\n**Severity:** Info | **Code Fix:** Wrap in try-catch\n\nDetects any method or member that contains throw statements.\n\n```csharp\n// Before\nvoid ProcessData(string data)\n{\n    if (string.IsNullOrEmpty(data))\n        throw new ArgumentException(\"Data cannot be empty\");\n}\n\n// After (Code Fix Applied)\nvoid ProcessData(string data)\n{\n    try\n    {\n        if (string.IsNullOrEmpty(data))\n            throw new ArgumentException(\"Data cannot be empty\");\n    }\n    catch (ArgumentException ex)\n    {\n        // Handle exception\n        throw;\n    }\n}\n```\n\n#### THROWS002: Unhandled throw statement\n**Severity:** Warning | **Code Fix:** Wrap in try-catch\n\nDetects throw statements not wrapped in try-catch blocks.\n\n```csharp\n// Before\nvoid SaveFile(string path, string content)\n{\n    File.WriteAllText(path, content); // Throws IOException\n}\n\n// After (Code Fix Applied)\nvoid SaveFile(string path, string content)\n{\n    try\n    {\n        File.WriteAllText(path, content);\n    }\n    catch (IOException ex)\n    {\n        // Handle exception\n        throw;\n    }\n}\n```\n\n#### THROWS003: Method contains try-catch block\n**Severity:** Info | **Code Fix:** Remove try-catch or add logging\n\nFlags methods containing try-catch blocks for tracking exception handling.\n\n#### THROWS004: Rethrow anti-pattern\n**Severity:** Warning | **Code Fix:** Fix rethrow\n\nDetects `throw ex;` which resets the stack trace. Should use `throw;` instead.\n\n```csharp\n// Before - WRONG (resets stack trace)\ntry\n{\n    DoSomething();\n}\ncatch (Exception ex)\n{\n    throw ex; // ❌ Resets stack trace\n}\n\n// After (Code Fix Applied) - CORRECT\ntry\n{\n    DoSomething();\n}\ncatch (Exception ex)\n{\n    throw; // ✓ Preserves stack trace\n}\n```\n\n#### THROWS007: Unreachable catch clause\n**Severity:** Warning | **Code Fix:** Reorder catches\n\nDetects catch clauses that can never be reached due to ordering.\n\n```csharp\n// Before - WRONG (InvalidOperationException is unreachable)\ntry\n{\n    DoSomething();\n}\ncatch (Exception ex) // ❌ Catches everything\n{\n    Log(ex);\n}\ncatch (InvalidOperationException ex) // Never reached\n{\n    LogSpecific(ex);\n}\n\n// After (Code Fix Applied) - CORRECT\ntry\n{\n    DoSomething();\n}\ncatch (InvalidOperationException ex) // ✓ Specific first\n{\n    LogSpecific(ex);\n}\ncatch (Exception ex)\n{\n    Log(ex);\n}\n```\n\n#### THROWS008: Empty catch block\n**Severity:** Warning | **Code Fix:** Add logging or remove\n\nDetects empty catch blocks that silently swallow exceptions.\n\n```csharp\n// Before - WRONG\ntry\n{\n    LoadConfiguration();\n}\ncatch (Exception)\n{\n    // ❌ Empty catch swallows exceptions\n}\n\n// After (Code Fix: Add Logging)\ntry\n{\n    LoadConfiguration();\n}\ncatch (Exception ex)\n{\n    Logger.LogError(ex, \"Failed to load configuration\"); // ✓ Logs error\n    throw;\n}\n```\n\n#### THROWS009: Catch block only rethrows\n**Severity:** Info | **Code Fix:** Remove unnecessary catch\n\nDetects catch blocks that only rethrow without doing any work.\n\n```csharp\n// Before - Unnecessary\ntry\n{\n    ProcessData();\n}\ncatch (Exception ex)\n{\n    throw; // No work done, catch is unnecessary\n}\n\n// After (Code Fix Applied)\nProcessData(); // ✓ Simplified\n```\n\n#### THROWS010: Overly broad exception catch\n**Severity:** Info | **Code Fix:** Add exception filter\n\nDetects catching `System.Exception` or `System.SystemException`.\n\n```csharp\n// Before - Too broad\ntry\n{\n    ParseUserInput(input);\n}\ncatch (Exception ex) // ❌ Catches everything\n{\n    LogError(ex);\n}\n\n// After (Code Fix: Add Filter)\ntry\n{\n    ParseUserInput(input);\n}\ncatch (Exception ex) when (ex is FormatException || ex is ArgumentException) // ✓ Specific\n{\n    LogError(ex);\n}\n```\n\n### Category 2: Exception Flow Analysis (3 rules)\n\n#### THROWS017: Unhandled method call\n**Severity:** Info\n\nDetects method calls that may throw exceptions without try-catch handling.\n\n```csharp\n// Detected\nvoid ProcessFile(string path)\n{\n    var content = File.ReadAllText(path); // May throw IOException\n    Process(content);\n}\n\n// Recommended\nvoid ProcessFile(string path)\n{\n    try\n    {\n        var content = File.ReadAllText(path);\n        Process(content);\n    }\n    catch (IOException ex)\n    {\n        Logger.LogError(ex, \"Failed to read file: {Path}\", path);\n        throw;\n    }\n}\n```\n\n#### THROWS018: Deep exception propagation\n**Severity:** Info\n\nDetects exceptions propagating through many call stack levels.\n\n#### THROWS019: Undocumented public API exception\n**Severity:** Warning | **Code Fix:** Add XML documentation\n\nDetects public methods that throw exceptions without XML documentation.\n\n```csharp\n// Before - Missing documentation\npublic void ValidateUser(string username)\n{\n    if (string.IsNullOrEmpty(username))\n        throw new ArgumentException(\"Username required\");\n}\n\n// After (Code Fix Applied)\n/// \u003csummary\u003e\n/// Validates the specified username.\n/// \u003c/summary\u003e\n/// \u003cparam name=\"username\"\u003eThe username to validate.\u003c/param\u003e\n/// \u003cexception cref=\"ArgumentException\"\u003e\n/// Thrown when \u003cparamref name=\"username\"/\u003e is null or empty.\n/// \u003c/exception\u003e\npublic void ValidateUser(string username)\n{\n    if (string.IsNullOrEmpty(username))\n        throw new ArgumentException(\"Username required\");\n}\n```\n\n### Category 3: Async Exception Patterns (3 rules)\n\n#### THROWS020: Async method throws synchronously\n**Severity:** Warning\n\nDetects async methods that throw exceptions before the first await.\n\n```csharp\n// Before - WRONG (throws before async)\nasync Task\u003cstring\u003e LoadDataAsync(string id)\n{\n    if (string.IsNullOrEmpty(id))\n        throw new ArgumentException(); // ❌ Synchronous throw\n\n    return await LoadFromDatabaseAsync(id);\n}\n\n// After - CORRECT\nasync Task\u003cstring\u003e LoadDataAsync(string id)\n{\n    if (string.IsNullOrEmpty(id))\n        return Task.FromException\u003cstring\u003e(\n            new ArgumentException()); // ✓ Returns faulted task\n\n    return await LoadFromDatabaseAsync(id);\n}\n```\n\n#### THROWS021: Async void exception\n**Severity:** Error | **Code Fix:** Convert to async Task\n\nDetects async void methods that can crash the application if they throw.\n\n```csharp\n// Before - WRONG (can crash app)\nasync void LoadDataButton_Click(object sender, EventArgs e)\n{\n    await LoadDataAsync(); // ❌ Exception crashes app\n}\n\n// After (Code Fix Applied) - CORRECT\nasync Task LoadDataButton_Click(object sender, EventArgs e)\n{\n    try\n    {\n        await LoadDataAsync(); // ✓ Exception can be handled\n    }\n    catch (Exception ex)\n    {\n        ShowError(ex.Message);\n    }\n}\n```\n\n#### THROWS022: Unobserved Task exception\n**Severity:** Warning | **Code Fix:** Add await or continuation\n\nDetects Task-returning methods called without await or exception handling.\n\n```csharp\n// Before - WRONG (exception unobserved)\nvoid ProcessData()\n{\n    LoadDataAsync(); // ❌ Exception lost\n}\n\n// After (Code Fix Applied) - CORRECT\nasync Task ProcessData()\n{\n    await LoadDataAsync(); // ✓ Exception propagates\n}\n```\n\n### Category 4: Iterator Exception Patterns (2 rules)\n\n#### THROWS023: Iterator deferred exception\n**Severity:** Info | **Code Fix:** Move validation outside iterator\n\nDetects exceptions in yield-based iterators that are deferred until enumeration.\n\n```csharp\n// Before - WRONG (exception deferred)\nIEnumerable\u003cint\u003e GetNumbers(int count)\n{\n    if (count \u003c 0)\n        throw new ArgumentException(); // ❌ Thrown during enumeration\n\n    for (int i = 0; i \u003c count; i++)\n        yield return i;\n}\n\n// After (Code Fix Applied) - CORRECT\nIEnumerable\u003cint\u003e GetNumbers(int count)\n{\n    if (count \u003c 0)\n        throw new ArgumentException(); // ✓ Thrown immediately\n\n    return GetNumbersIterator(count);\n}\n\nIEnumerable\u003cint\u003e GetNumbersIterator(int count)\n{\n    for (int i = 0; i \u003c count; i++)\n        yield return i;\n}\n```\n\n#### THROWS024: Iterator try-finally issue\n**Severity:** Warning | **Code Fix:** Add proper cleanup\n\nDetects try-finally issues in iterators where finally may not execute.\n\n### Category 5: Lambda Exception Patterns (2 rules)\n\n#### THROWS025: Lambda uncaught exception\n**Severity:** Warning | **Code Fix:** Wrap in try-catch\n\nDetects lambdas that throw exceptions without proper handling.\n\n```csharp\n// Before - WRONG (exception propagates to LINQ)\nvar results = items.Select(x =\u003e {\n    if (x == null)\n        throw new ArgumentNullException(); // ❌ Crashes enumeration\n    return x.Value;\n});\n\n// After (Code Fix Applied) - CORRECT\nvar results = items.Select(x =\u003e {\n    try\n    {\n        if (x == null)\n            throw new ArgumentNullException();\n        return x.Value;\n    }\n    catch (ArgumentNullException ex)\n    {\n        Logger.LogError(ex, \"Null item in collection\");\n        return default;\n    }\n});\n```\n\n#### THROWS026: Event handler lambda exception\n**Severity:** Error | **Code Fix:** Wrap in try-catch\n\nDetects event handler lambdas that throw unhandled exceptions.\n\n```csharp\n// Before - WRONG (can crash app)\nbutton.Click += (sender, e) =\u003e {\n    throw new InvalidOperationException(); // ❌ Crashes app\n};\n\n// After (Code Fix Applied) - CORRECT\nbutton.Click += (sender, e) =\u003e {\n    try\n    {\n        ProcessClick();\n    }\n    catch (InvalidOperationException ex)\n    {\n        MessageBox.Show(ex.Message); // ✓ Handled gracefully\n    }\n};\n```\n\n### Category 6: Best Practices (4 rules)\n\n#### THROWS027: Exception used for control flow\n**Severity:** Info | **Code Fix:** Refactor to use return values\n\nDetects exceptions used for normal control flow instead of return values.\n\n```csharp\n// Before - WRONG (exception for control flow)\ntry\n{\n    var user = FindUser(id);\n    if (user == null)\n        throw new UserNotFoundException(); // ❌ Expected condition\n}\ncatch (UserNotFoundException)\n{\n    CreateDefaultUser(id);\n}\n\n// After (Code Fix Applied) - CORRECT\nvar user = FindUser(id);\nif (user == null) // ✓ Return value check\n{\n    CreateDefaultUser(id);\n}\n```\n\n#### THROWS028: Custom exception naming violation\n**Severity:** Info | **Code Fix:** Rename exception\n\nDetects custom exception types not ending with \"Exception\".\n\n```csharp\n// Before - WRONG\npublic class UserNotFound : Exception { } // ❌ Missing \"Exception\"\n\n// After (Code Fix Applied) - CORRECT\npublic class UserNotFoundException : Exception { } // ✓ Follows convention\n```\n\n#### THROWS029: Exception in hot path\n**Severity:** Warning | **Code Fix:** Suggest refactoring\n\nDetects exceptions thrown inside loops (performance issue).\n\n```csharp\n// Before - WRONG (exception in loop)\nfor (int i = 0; i \u003c items.Count; i++)\n{\n    if (items[i] == null)\n        throw new ArgumentNullException(); // ❌ Performance issue\n    Process(items[i]);\n}\n\n// After (Code Fix Applied) - CORRECT\n// Validate before loop\nfor (int i = 0; i \u003c items.Count; i++)\n{\n    if (items[i] == null)\n        continue; // ✓ Or validate before loop starts\n    Process(items[i]);\n}\n```\n\n#### THROWS030: Consider Result pattern\n**Severity:** Info | **Code Fix:** Suggest Result\u003cT\u003e\n\nSuggests using Result\u003cT\u003e pattern for expected error conditions.\n\n```csharp\n// Before - Using exceptions for expected failures\npublic User ParseUser(string data)\n{\n    if (string.IsNullOrEmpty(data))\n        throw new FormatException(); // Expected condition\n    return JsonSerializer.Deserialize\u003cUser\u003e(data);\n}\n\n// After - Using Result\u003cT\u003e pattern (suggested)\npublic Result\u003cUser\u003e ParseUser(string data)\n{\n    if (string.IsNullOrEmpty(data))\n        return Result\u003cUser\u003e.Failure(\"Data cannot be empty\");\n\n    try\n    {\n        return Result\u003cUser\u003e.Success(\n            JsonSerializer.Deserialize\u003cUser\u003e(data));\n    }\n    catch (JsonException ex)\n    {\n        return Result\u003cUser\u003e.Failure(ex.Message);\n    }\n}\n```\n\n## Examples and Samples\n\nFor comprehensive examples demonstrating all diagnostics and code fixes, see:\n- [ExceptionPatterns Sample](samples/ExceptionPatterns/) - Demonstrates all 30 diagnostics\n- [LibraryManagement Sample](samples/LibraryManagement/) - Real-world library management system\n\n## RoslynAnalyzer.Core\n\nRoslynAnalyzer.Core is a reusable infrastructure library extracted from ThrowsAnalyzer for building custom Roslyn analyzers. It provides battle-tested components for common analyzer patterns.\n\n### Installation\n\n```bash\ndotnet add package RoslynAnalyzer.Core\n```\n\n### Features\n\n- **Call Graph Analysis** - Track method invocations with cycle detection and transitive operations\n- **Executable Member Detection** - Identify all C# member types (methods, constructors, properties, lambdas, local functions, etc.)\n- **Async/Await Pattern Detection** - Analyze async methods, detect async void, find awaits, and identify unawaited tasks\n- **Iterator Pattern Detection** - Detect yield-based iterators, find yield statements, and analyze iterator flow\n- **Lambda Expression Analysis** - Generic lambda detection with context identification (event handlers, LINQ, Task.Run, callbacks)\n- **Type Hierarchy Analysis** - Navigate type hierarchies and check interface implementations\n- **Configuration Infrastructure** - Read .editorconfig settings for analyzer customization\n- **Suppression Infrastructure** - Support custom suppression attributes\n- **Performance Optimizations** - Compilation and symbol caching with statistics\n\n### Quick Start\n\n```csharp\nusing RoslynAnalyzer.Core.Analysis.Patterns.Async;\nusing RoslynAnalyzer.Core.Analysis.Patterns.Iterators;\nusing RoslynAnalyzer.Core.Analysis.Patterns.Lambda;\n\n// Async pattern detection\nvar asyncInfo = AsyncMethodDetector.GetAsyncMethodInfo(methodSymbol, methodNode, semanticModel);\nif (asyncInfo.IsAsyncVoid)\n{\n    // Handle async void pattern\n}\n\n// Iterator pattern detection\nif (IteratorMethodDetector.IsIteratorMethod(methodSymbol, methodNode))\n{\n    var yieldStatements = IteratorMethodDetector.GetYieldStatements(methodBody);\n    // Analyze iterator pattern\n}\n\n// Lambda pattern detection\nvar lambdas = LambdaDetector.GetLambdaExpressions(methodBody);\nforeach (var lambda in lambdas)\n{\n    var context = LambdaDetector.GetLambdaContext(lambda, semanticModel);\n    if (context == LambdaContext.EventHandler)\n    {\n        // Handle event handler lambda\n    }\n}\n```\n\n### Documentation\n\nFor complete API reference, examples, and usage guides, see the [RoslynAnalyzer.Core README](src/RoslynAnalyzer.Core/README.md).\n\n### Real-World Example\n\nThrowsAnalyzer itself is built using RoslynAnalyzer.Core, providing a comprehensive real-world example of how to use the library to build production-ready analyzers.\n\n## Building from Source\n\n```bash\n# Clone the repository\ngit clone https://github.com/wieslawsoltes/ThrowsAnalyzer.git\ncd ThrowsAnalyzer\n\n# Build everything\ndotnet build\n\n# Run all tests (461 tests)\ndotnet test\n\n# Build individual projects\ndotnet build src/ThrowsAnalyzer/ThrowsAnalyzer.csproj\ndotnet build src/RoslynAnalyzer.Core/RoslynAnalyzer.Core.csproj\n\n# Run specific test projects\ndotnet test tests/ThrowsAnalyzer.Tests/ThrowsAnalyzer.Tests.csproj\ndotnet test tests/RoslynAnalyzer.Core.Tests/RoslynAnalyzer.Core.Tests.csproj\n\n# Create NuGet packages\ndotnet pack -c Release -o nupkg\n```\n\n## Project Structure\n\n```\nThrowsAnalyzer/\n├── src/\n│   ├── ThrowsAnalyzer/           # Main analyzer with 30 diagnostics and 16 code fixes\n│   ├── ThrowsAnalyzer.Cli/       # Command-line tool for project analysis\n│   └── RoslynAnalyzer.Core/      # Reusable infrastructure library\n├── tests/\n│   ├── ThrowsAnalyzer.Tests/     # 274 tests for ThrowsAnalyzer\n│   └── RoslynAnalyzer.Core.Tests/ # 187 tests for RoslynAnalyzer.Core\n├── samples/\n│   ├── ExceptionPatterns/        # Demonstrates all 30 diagnostics\n│   └── LibraryManagement/        # Real-world example application\n└── docs/                         # Documentation and guides\n```\n\n## Contributing\n\nContributions are welcome! Please feel free to submit issues, feature requests, or pull requests.\n\n## License\n\nThis project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwieslawsoltes%2Fthrowsanalyzer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwieslawsoltes%2Fthrowsanalyzer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwieslawsoltes%2Fthrowsanalyzer/lists"}