https://github.com/wieslawsoltes/throwsanalyzer
A Roslyn-based C# analyzer that detects exception handling patterns in your code
https://github.com/wieslawsoltes/throwsanalyzer
csharp dotnet exception-reporting exceptions roslyn roslyn-analyzer roslyn-analyzers
Last synced: 8 months ago
JSON representation
A Roslyn-based C# analyzer that detects exception handling patterns in your code
- Host: GitHub
- URL: https://github.com/wieslawsoltes/throwsanalyzer
- Owner: wieslawsoltes
- License: mit
- Created: 2025-10-24T17:58:28.000Z (8 months ago)
- Default Branch: main
- Last Pushed: 2025-10-27T13:19:39.000Z (8 months ago)
- Last Synced: 2025-10-27T13:27:21.670Z (8 months ago)
- Topics: csharp, dotnet, exception-reporting, exceptions, roslyn, roslyn-analyzer, roslyn-analyzers
- Language: C#
- Homepage:
- Size: 300 KB
- Stars: 2
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# ThrowsAnalyzer
A 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.
## Build Status & NuGet Packages
| Package | Version | Downloads | Build |
|---------|---------|-----------|-------|
| **ThrowsAnalyzer** | [](https://www.nuget.org/packages/ThrowsAnalyzer) | [](https://www.nuget.org/packages/ThrowsAnalyzer) | [](https://github.com/wieslawsoltes/ThrowsAnalyzer/actions/workflows/ci.yml) |
| **ThrowsAnalyzer.Cli** | [](https://www.nuget.org/packages/ThrowsAnalyzer.Cli) | [](https://www.nuget.org/packages/ThrowsAnalyzer.Cli) | [](https://github.com/wieslawsoltes/ThrowsAnalyzer/actions/workflows/ci.yml) |
| **RoslynAnalyzer.Core** | [](https://www.nuget.org/packages/RoslynAnalyzer.Core) | [](https://www.nuget.org/packages/RoslynAnalyzer.Core) | [](https://github.com/wieslawsoltes/ThrowsAnalyzer/actions/workflows/ci.yml) |
[](LICENSE)
## Repository Contents
This repository includes three packages:
- **[ThrowsAnalyzer](#throwsanalyzer-1)** - Comprehensive exception analysis with 30 diagnostics and 16 code fixes
- **[ThrowsAnalyzer.Cli](#cli-tool)** - Command-line tool for analyzing projects and generating reports
- **[RoslynAnalyzer.Core](#roslynanalyzercore)** - Reusable infrastructure for building custom Roslyn analyzers
## Features
ThrowsAnalyzer provides **30 diagnostic rules** organized into 6 categories, with **16 automated code fixes** for quick issue resolution.
### Diagnostic Rules Summary
| Category | Diagnostics | Description |
|----------|------------|-------------|
| **Basic Exception Handling** | THROWS001-003, 004, 007-010 | Fundamental exception patterns and anti-patterns |
| **Exception Flow Analysis** | THROWS017-019 | Method call exception propagation and documentation |
| **Async Exception Patterns** | THROWS020-022 | Async/await exception handling issues |
| **Iterator Exception Patterns** | THROWS023-024 | Exception handling in yield-based iterators |
| **Lambda Exception Patterns** | THROWS025-026 | Exception handling in lambda expressions |
| **Best Practices** | THROWS027-030 | Design patterns and performance recommendations |
### Code Fixes Summary
| Code Fix | Diagnostics | Actions |
|----------|-------------|---------|
| **Wrap in try-catch** | THROWS001, THROWS002 | Adds try-catch around throwing code |
| **Fix rethrow** | THROWS004 | Converts `throw ex;` to `throw;` |
| **Reorder catches** | THROWS007 | Reorders catch clauses from specific to general |
| **Add/Remove logging** | THROWS008, THROWS003 | Adds logging or removes empty catch |
| **Remove rethrow-only catch** | THROWS009 | Removes unnecessary catch blocks |
| **Add exception filter** | THROWS010 | Adds `when` clause to specific catches |
| **Convert async void** | THROWS021 | Converts async void to async Task |
| **Add Task observation** | THROWS022 | Adds await or continuation |
| **Wrap iterator validation** | THROWS023 | Moves validation outside iterator |
| **Add try-finally** | THROWS024 | Adds try-finally for cleanup |
| **Wrap lambda in try-catch** | THROWS025, THROWS026 | Adds exception handling to lambdas |
| **Refactor control flow** | THROWS027 | Suggests return value instead of exceptions |
| **Rename exception** | THROWS028 | Renames to follow convention |
| **Move to cold path** | THROWS029 | Suggests refactoring for performance |
| **Add XML docs** | THROWS019 | Documents thrown exceptions |
| **Suggest Result pattern** | THROWS030 | Suggests Result for error handling |
## Supported Member Types
ThrowsAnalyzer analyzes exception handling patterns in:
- Methods
- Constructors and Destructors
- Properties (including expression-bodied properties)
- Property Accessors (get, set, init, add, remove)
- Operators (binary, unary, conversion)
- Local Functions
- Lambda Expressions (simple and parenthesized)
- Anonymous Methods
## Installation
### Analyzer Library
Add the analyzer to your project via NuGet:
```bash
dotnet add package ThrowsAnalyzer
```
Once installed, the analyzer runs automatically during compilation. Diagnostics will appear in your IDE and build output.
### CLI Tool
Install the command-line tool globally to analyze projects and generate reports:
```bash
dotnet tool install --global ThrowsAnalyzer.Cli
```
#### CLI Quick Start
```bash
# Analyze a project and generate reports
throws-analyzer analyze MyProject.csproj
# Analyze a solution
throws-analyzer analyze MySolution.sln
# Generate HTML and Markdown reports
throws-analyzer analyze MyProject.csproj --verbose --open
```
The CLI tool generates comprehensive reports showing:
- Summary statistics by diagnostic ID, project, severity, and file
- Interactive HTML reports with sortable tables
- Markdown reports for documentation
- Detailed diagnostics with code snippets
See [CLI Tool Documentation](docs/guides/CLI_TOOL.md) for complete usage guide and CI/CD integration examples.
## Configuration
ThrowsAnalyzer provides granular configuration options through `.editorconfig` files. You can control analyzer enablement, severity, and which member types to analyze.
### Enabling/Disabling Individual Analyzers
Control whether each analyzer is completely enabled or disabled:
```ini
[*.cs]
# Enable/disable throw statement analyzer (THROWS001)
throws_analyzer_enable_throw_statement = true
# Enable/disable unhandled throw analyzer (THROWS002)
throws_analyzer_enable_unhandled_throw = true
# Enable/disable try-catch block analyzer (THROWS003)
throws_analyzer_enable_try_catch = true
```
All analyzers are enabled by default. Setting an option to `false` completely disables that analyzer, regardless of severity settings.
### Configuring Analyzer Severity
Control the severity of each diagnostic rule:
```ini
[*.cs]
# Basic analyzers
# THROWS001: Detects throw statements in members
dotnet_diagnostic.THROWS001.severity = suggestion
# THROWS002: Detects unhandled throw statements (not wrapped in try-catch)
dotnet_diagnostic.THROWS002.severity = warning
# THROWS003: Detects try-catch blocks in members
dotnet_diagnostic.THROWS003.severity = suggestion
# Advanced type-aware analyzers
# THROWS004: Rethrow anti-pattern (throw ex; instead of throw;)
dotnet_diagnostic.THROWS004.severity = warning
# THROWS007: Unreachable catch clause due to ordering
dotnet_diagnostic.THROWS007.severity = warning
# THROWS008: Empty catch block swallows exceptions
dotnet_diagnostic.THROWS008.severity = warning
# THROWS009: Catch block only rethrows exception
dotnet_diagnostic.THROWS009.severity = suggestion
# THROWS010: Overly broad exception catch
dotnet_diagnostic.THROWS010.severity = suggestion
```
**Severity options:** `none`, `silent`, `suggestion`, `warning`, `error`
### Configuring Member Type Analysis
Selectively enable or disable analysis for specific member types:
```ini
[*.cs]
# Analyze regular methods
throws_analyzer_analyze_methods = true
# Analyze constructors
throws_analyzer_analyze_constructors = true
# Analyze destructors/finalizers
throws_analyzer_analyze_destructors = true
# Analyze operator overloads
throws_analyzer_analyze_operators = true
# Analyze conversion operators (implicit/explicit)
throws_analyzer_analyze_conversion_operators = true
# Analyze properties (expression-bodied properties)
throws_analyzer_analyze_properties = true
# Analyze property accessors (get, set, init, add, remove)
throws_analyzer_analyze_accessors = true
# Analyze local functions
throws_analyzer_analyze_local_functions = true
# Analyze lambda expressions
throws_analyzer_analyze_lambdas = true
# Analyze anonymous methods (delegate { } syntax)
throws_analyzer_analyze_anonymous_methods = true
```
All member types are analyzed by default. Set any option to `false` to disable analysis for that member type.
### Example Configurations
#### Minimal Configuration (Methods and Constructors Only)
```ini
[*.cs]
throws_analyzer_analyze_methods = true
throws_analyzer_analyze_constructors = true
throws_analyzer_analyze_destructors = false
throws_analyzer_analyze_operators = false
throws_analyzer_analyze_conversion_operators = false
throws_analyzer_analyze_properties = false
throws_analyzer_analyze_accessors = false
throws_analyzer_analyze_local_functions = false
throws_analyzer_analyze_lambdas = false
throws_analyzer_analyze_anonymous_methods = false
```
#### Focus on Unhandled Exceptions Only
```ini
[*.cs]
throws_analyzer_enable_throw_statement = false
throws_analyzer_enable_unhandled_throw = true
throws_analyzer_enable_try_catch = false
dotnet_diagnostic.THROWS002.severity = error
```
#### Disable Analysis for Lambdas and Local Functions
```ini
[*.cs]
throws_analyzer_analyze_local_functions = false
throws_analyzer_analyze_lambdas = false
throws_analyzer_analyze_anonymous_methods = false
```
See [.editorconfig.example](.editorconfig.example) for a complete configuration template.
## Complete Diagnostic Reference
### Category 1: Basic Exception Handling (8 rules)
#### THROWS001: Method contains throw statement
**Severity:** Info | **Code Fix:** Wrap in try-catch
Detects any method or member that contains throw statements.
```csharp
// Before
void ProcessData(string data)
{
if (string.IsNullOrEmpty(data))
throw new ArgumentException("Data cannot be empty");
}
// After (Code Fix Applied)
void ProcessData(string data)
{
try
{
if (string.IsNullOrEmpty(data))
throw new ArgumentException("Data cannot be empty");
}
catch (ArgumentException ex)
{
// Handle exception
throw;
}
}
```
#### THROWS002: Unhandled throw statement
**Severity:** Warning | **Code Fix:** Wrap in try-catch
Detects throw statements not wrapped in try-catch blocks.
```csharp
// Before
void SaveFile(string path, string content)
{
File.WriteAllText(path, content); // Throws IOException
}
// After (Code Fix Applied)
void SaveFile(string path, string content)
{
try
{
File.WriteAllText(path, content);
}
catch (IOException ex)
{
// Handle exception
throw;
}
}
```
#### THROWS003: Method contains try-catch block
**Severity:** Info | **Code Fix:** Remove try-catch or add logging
Flags methods containing try-catch blocks for tracking exception handling.
#### THROWS004: Rethrow anti-pattern
**Severity:** Warning | **Code Fix:** Fix rethrow
Detects `throw ex;` which resets the stack trace. Should use `throw;` instead.
```csharp
// Before - WRONG (resets stack trace)
try
{
DoSomething();
}
catch (Exception ex)
{
throw ex; // ❌ Resets stack trace
}
// After (Code Fix Applied) - CORRECT
try
{
DoSomething();
}
catch (Exception ex)
{
throw; // ✓ Preserves stack trace
}
```
#### THROWS007: Unreachable catch clause
**Severity:** Warning | **Code Fix:** Reorder catches
Detects catch clauses that can never be reached due to ordering.
```csharp
// Before - WRONG (InvalidOperationException is unreachable)
try
{
DoSomething();
}
catch (Exception ex) // ❌ Catches everything
{
Log(ex);
}
catch (InvalidOperationException ex) // Never reached
{
LogSpecific(ex);
}
// After (Code Fix Applied) - CORRECT
try
{
DoSomething();
}
catch (InvalidOperationException ex) // ✓ Specific first
{
LogSpecific(ex);
}
catch (Exception ex)
{
Log(ex);
}
```
#### THROWS008: Empty catch block
**Severity:** Warning | **Code Fix:** Add logging or remove
Detects empty catch blocks that silently swallow exceptions.
```csharp
// Before - WRONG
try
{
LoadConfiguration();
}
catch (Exception)
{
// ❌ Empty catch swallows exceptions
}
// After (Code Fix: Add Logging)
try
{
LoadConfiguration();
}
catch (Exception ex)
{
Logger.LogError(ex, "Failed to load configuration"); // ✓ Logs error
throw;
}
```
#### THROWS009: Catch block only rethrows
**Severity:** Info | **Code Fix:** Remove unnecessary catch
Detects catch blocks that only rethrow without doing any work.
```csharp
// Before - Unnecessary
try
{
ProcessData();
}
catch (Exception ex)
{
throw; // No work done, catch is unnecessary
}
// After (Code Fix Applied)
ProcessData(); // ✓ Simplified
```
#### THROWS010: Overly broad exception catch
**Severity:** Info | **Code Fix:** Add exception filter
Detects catching `System.Exception` or `System.SystemException`.
```csharp
// Before - Too broad
try
{
ParseUserInput(input);
}
catch (Exception ex) // ❌ Catches everything
{
LogError(ex);
}
// After (Code Fix: Add Filter)
try
{
ParseUserInput(input);
}
catch (Exception ex) when (ex is FormatException || ex is ArgumentException) // ✓ Specific
{
LogError(ex);
}
```
### Category 2: Exception Flow Analysis (3 rules)
#### THROWS017: Unhandled method call
**Severity:** Info
Detects method calls that may throw exceptions without try-catch handling.
```csharp
// Detected
void ProcessFile(string path)
{
var content = File.ReadAllText(path); // May throw IOException
Process(content);
}
// Recommended
void ProcessFile(string path)
{
try
{
var content = File.ReadAllText(path);
Process(content);
}
catch (IOException ex)
{
Logger.LogError(ex, "Failed to read file: {Path}", path);
throw;
}
}
```
#### THROWS018: Deep exception propagation
**Severity:** Info
Detects exceptions propagating through many call stack levels.
#### THROWS019: Undocumented public API exception
**Severity:** Warning | **Code Fix:** Add XML documentation
Detects public methods that throw exceptions without XML documentation.
```csharp
// Before - Missing documentation
public void ValidateUser(string username)
{
if (string.IsNullOrEmpty(username))
throw new ArgumentException("Username required");
}
// After (Code Fix Applied)
///
/// Validates the specified username.
///
/// The username to validate.
///
/// Thrown when is null or empty.
///
public void ValidateUser(string username)
{
if (string.IsNullOrEmpty(username))
throw new ArgumentException("Username required");
}
```
### Category 3: Async Exception Patterns (3 rules)
#### THROWS020: Async method throws synchronously
**Severity:** Warning
Detects async methods that throw exceptions before the first await.
```csharp
// Before - WRONG (throws before async)
async Task LoadDataAsync(string id)
{
if (string.IsNullOrEmpty(id))
throw new ArgumentException(); // ❌ Synchronous throw
return await LoadFromDatabaseAsync(id);
}
// After - CORRECT
async Task LoadDataAsync(string id)
{
if (string.IsNullOrEmpty(id))
return Task.FromException(
new ArgumentException()); // ✓ Returns faulted task
return await LoadFromDatabaseAsync(id);
}
```
#### THROWS021: Async void exception
**Severity:** Error | **Code Fix:** Convert to async Task
Detects async void methods that can crash the application if they throw.
```csharp
// Before - WRONG (can crash app)
async void LoadDataButton_Click(object sender, EventArgs e)
{
await LoadDataAsync(); // ❌ Exception crashes app
}
// After (Code Fix Applied) - CORRECT
async Task LoadDataButton_Click(object sender, EventArgs e)
{
try
{
await LoadDataAsync(); // ✓ Exception can be handled
}
catch (Exception ex)
{
ShowError(ex.Message);
}
}
```
#### THROWS022: Unobserved Task exception
**Severity:** Warning | **Code Fix:** Add await or continuation
Detects Task-returning methods called without await or exception handling.
```csharp
// Before - WRONG (exception unobserved)
void ProcessData()
{
LoadDataAsync(); // ❌ Exception lost
}
// After (Code Fix Applied) - CORRECT
async Task ProcessData()
{
await LoadDataAsync(); // ✓ Exception propagates
}
```
### Category 4: Iterator Exception Patterns (2 rules)
#### THROWS023: Iterator deferred exception
**Severity:** Info | **Code Fix:** Move validation outside iterator
Detects exceptions in yield-based iterators that are deferred until enumeration.
```csharp
// Before - WRONG (exception deferred)
IEnumerable GetNumbers(int count)
{
if (count < 0)
throw new ArgumentException(); // ❌ Thrown during enumeration
for (int i = 0; i < count; i++)
yield return i;
}
// After (Code Fix Applied) - CORRECT
IEnumerable GetNumbers(int count)
{
if (count < 0)
throw new ArgumentException(); // ✓ Thrown immediately
return GetNumbersIterator(count);
}
IEnumerable GetNumbersIterator(int count)
{
for (int i = 0; i < count; i++)
yield return i;
}
```
#### THROWS024: Iterator try-finally issue
**Severity:** Warning | **Code Fix:** Add proper cleanup
Detects try-finally issues in iterators where finally may not execute.
### Category 5: Lambda Exception Patterns (2 rules)
#### THROWS025: Lambda uncaught exception
**Severity:** Warning | **Code Fix:** Wrap in try-catch
Detects lambdas that throw exceptions without proper handling.
```csharp
// Before - WRONG (exception propagates to LINQ)
var results = items.Select(x => {
if (x == null)
throw new ArgumentNullException(); // ❌ Crashes enumeration
return x.Value;
});
// After (Code Fix Applied) - CORRECT
var results = items.Select(x => {
try
{
if (x == null)
throw new ArgumentNullException();
return x.Value;
}
catch (ArgumentNullException ex)
{
Logger.LogError(ex, "Null item in collection");
return default;
}
});
```
#### THROWS026: Event handler lambda exception
**Severity:** Error | **Code Fix:** Wrap in try-catch
Detects event handler lambdas that throw unhandled exceptions.
```csharp
// Before - WRONG (can crash app)
button.Click += (sender, e) => {
throw new InvalidOperationException(); // ❌ Crashes app
};
// After (Code Fix Applied) - CORRECT
button.Click += (sender, e) => {
try
{
ProcessClick();
}
catch (InvalidOperationException ex)
{
MessageBox.Show(ex.Message); // ✓ Handled gracefully
}
};
```
### Category 6: Best Practices (4 rules)
#### THROWS027: Exception used for control flow
**Severity:** Info | **Code Fix:** Refactor to use return values
Detects exceptions used for normal control flow instead of return values.
```csharp
// Before - WRONG (exception for control flow)
try
{
var user = FindUser(id);
if (user == null)
throw new UserNotFoundException(); // ❌ Expected condition
}
catch (UserNotFoundException)
{
CreateDefaultUser(id);
}
// After (Code Fix Applied) - CORRECT
var user = FindUser(id);
if (user == null) // ✓ Return value check
{
CreateDefaultUser(id);
}
```
#### THROWS028: Custom exception naming violation
**Severity:** Info | **Code Fix:** Rename exception
Detects custom exception types not ending with "Exception".
```csharp
// Before - WRONG
public class UserNotFound : Exception { } // ❌ Missing "Exception"
// After (Code Fix Applied) - CORRECT
public class UserNotFoundException : Exception { } // ✓ Follows convention
```
#### THROWS029: Exception in hot path
**Severity:** Warning | **Code Fix:** Suggest refactoring
Detects exceptions thrown inside loops (performance issue).
```csharp
// Before - WRONG (exception in loop)
for (int i = 0; i < items.Count; i++)
{
if (items[i] == null)
throw new ArgumentNullException(); // ❌ Performance issue
Process(items[i]);
}
// After (Code Fix Applied) - CORRECT
// Validate before loop
for (int i = 0; i < items.Count; i++)
{
if (items[i] == null)
continue; // ✓ Or validate before loop starts
Process(items[i]);
}
```
#### THROWS030: Consider Result pattern
**Severity:** Info | **Code Fix:** Suggest Result
Suggests using Result pattern for expected error conditions.
```csharp
// Before - Using exceptions for expected failures
public User ParseUser(string data)
{
if (string.IsNullOrEmpty(data))
throw new FormatException(); // Expected condition
return JsonSerializer.Deserialize(data);
}
// After - Using Result pattern (suggested)
public Result ParseUser(string data)
{
if (string.IsNullOrEmpty(data))
return Result.Failure("Data cannot be empty");
try
{
return Result.Success(
JsonSerializer.Deserialize(data));
}
catch (JsonException ex)
{
return Result.Failure(ex.Message);
}
}
```
## Examples and Samples
For comprehensive examples demonstrating all diagnostics and code fixes, see:
- [ExceptionPatterns Sample](samples/ExceptionPatterns/) - Demonstrates all 30 diagnostics
- [LibraryManagement Sample](samples/LibraryManagement/) - Real-world library management system
## RoslynAnalyzer.Core
RoslynAnalyzer.Core is a reusable infrastructure library extracted from ThrowsAnalyzer for building custom Roslyn analyzers. It provides battle-tested components for common analyzer patterns.
### Installation
```bash
dotnet add package RoslynAnalyzer.Core
```
### Features
- **Call Graph Analysis** - Track method invocations with cycle detection and transitive operations
- **Executable Member Detection** - Identify all C# member types (methods, constructors, properties, lambdas, local functions, etc.)
- **Async/Await Pattern Detection** - Analyze async methods, detect async void, find awaits, and identify unawaited tasks
- **Iterator Pattern Detection** - Detect yield-based iterators, find yield statements, and analyze iterator flow
- **Lambda Expression Analysis** - Generic lambda detection with context identification (event handlers, LINQ, Task.Run, callbacks)
- **Type Hierarchy Analysis** - Navigate type hierarchies and check interface implementations
- **Configuration Infrastructure** - Read .editorconfig settings for analyzer customization
- **Suppression Infrastructure** - Support custom suppression attributes
- **Performance Optimizations** - Compilation and symbol caching with statistics
### Quick Start
```csharp
using RoslynAnalyzer.Core.Analysis.Patterns.Async;
using RoslynAnalyzer.Core.Analysis.Patterns.Iterators;
using RoslynAnalyzer.Core.Analysis.Patterns.Lambda;
// Async pattern detection
var asyncInfo = AsyncMethodDetector.GetAsyncMethodInfo(methodSymbol, methodNode, semanticModel);
if (asyncInfo.IsAsyncVoid)
{
// Handle async void pattern
}
// Iterator pattern detection
if (IteratorMethodDetector.IsIteratorMethod(methodSymbol, methodNode))
{
var yieldStatements = IteratorMethodDetector.GetYieldStatements(methodBody);
// Analyze iterator pattern
}
// Lambda pattern detection
var lambdas = LambdaDetector.GetLambdaExpressions(methodBody);
foreach (var lambda in lambdas)
{
var context = LambdaDetector.GetLambdaContext(lambda, semanticModel);
if (context == LambdaContext.EventHandler)
{
// Handle event handler lambda
}
}
```
### Documentation
For complete API reference, examples, and usage guides, see the [RoslynAnalyzer.Core README](src/RoslynAnalyzer.Core/README.md).
### Real-World Example
ThrowsAnalyzer itself is built using RoslynAnalyzer.Core, providing a comprehensive real-world example of how to use the library to build production-ready analyzers.
## Building from Source
```bash
# Clone the repository
git clone https://github.com/wieslawsoltes/ThrowsAnalyzer.git
cd ThrowsAnalyzer
# Build everything
dotnet build
# Run all tests (461 tests)
dotnet test
# Build individual projects
dotnet build src/ThrowsAnalyzer/ThrowsAnalyzer.csproj
dotnet build src/RoslynAnalyzer.Core/RoslynAnalyzer.Core.csproj
# Run specific test projects
dotnet test tests/ThrowsAnalyzer.Tests/ThrowsAnalyzer.Tests.csproj
dotnet test tests/RoslynAnalyzer.Core.Tests/RoslynAnalyzer.Core.Tests.csproj
# Create NuGet packages
dotnet pack -c Release -o nupkg
```
## Project Structure
```
ThrowsAnalyzer/
├── src/
│ ├── ThrowsAnalyzer/ # Main analyzer with 30 diagnostics and 16 code fixes
│ ├── ThrowsAnalyzer.Cli/ # Command-line tool for project analysis
│ └── RoslynAnalyzer.Core/ # Reusable infrastructure library
├── tests/
│ ├── ThrowsAnalyzer.Tests/ # 274 tests for ThrowsAnalyzer
│ └── RoslynAnalyzer.Core.Tests/ # 187 tests for RoslynAnalyzer.Core
├── samples/
│ ├── ExceptionPatterns/ # Demonstrates all 30 diagnostics
│ └── LibraryManagement/ # Real-world example application
└── docs/ # Documentation and guides
```
## Contributing
Contributions are welcome! Please feel free to submit issues, feature requests, or pull requests.
## License
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.