Skip to content

Commit f2eba42

Browse files
Add LINQ support for AsyncEnumerable (#290)
* Add AsyncEnumerable LINQ support * Document changelog requirement and update * Document async LINQ support * Document NuGet package README maintenance
1 parent d4d9f85 commit f2eba42

File tree

11 files changed

+123
-339
lines changed

11 files changed

+123
-339
lines changed

AGENTS.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,20 @@
2727
- Format each changed file using
2828
`dotnet format <path to dir of solution or project file> --no-restore --include <comma separated list with file paths>`
2929
to respect `.editorconfig` rules without triggering a restore.
30+
31+
## Changelog
32+
- Record your changes in `CHANGELOG.md` under the "Unreleased" section using the
33+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) style.
34+
- Reference the pull request in the form
35+
`- PR [#PR_NUMBER](https://github.com/marinasundstrom/CheckedExceptions/pull/PR_NUMBER) Description`.
36+
37+
## Documentation
38+
- Keep user-facing docs current. Update:
39+
- `README.md`
40+
- `docs/analyzer-specification.md`
41+
- `docs/codefix-specification.md`
42+
when changes affect functionality or user guidance.
43+
- NuGet package README:
44+
- `CheckedExceptions.Package/docs/README.md` is published to nuget.org.
45+
- Base its content on the root `README.md`, but keep it to a brief introduction and a short example.
46+
- Link back to the repository for full documentation and details.

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88

99
## Unreleased
1010

11+
### Added
12+
13+
- PR [#PR_NUMBER](https://github.com/marinasundstrom/CheckedExceptions/pull/PR_NUMBER) Handle LINQ methods on `AsyncEnumerable`
14+
15+
### Changed
16+
17+
- PR [#PR_NUMBER](https://github.com/marinasundstrom/CheckedExceptions/pull/PR_NUMBER) Trim NuGet package README and document maintenance guidelines
18+
1119
### Fixed
1220

1321
- PR [#287](https://github.com/marinasundstrom/CheckedExceptions/pull/287) Fix LINQ chain diagnostics
Lines changed: 7 additions & 321 deletions
Original file line numberDiff line numberDiff line change
@@ -1,337 +1,23 @@
11
# Checked Exceptions for C#
22

3-
**Take control of exception flowenforce explicit handling or declaration in C#**
3+
Take control of exception flowenforce explicit handling or declaration in C#.
44

5-
[![Build](https://github.com/marinasundstrom/CheckedExceptions/actions/workflows/ci.yml/badge.svg)]() [![NuGet](https://img.shields.io/nuget/v/Sundstrom.CheckedExceptions.svg)](https://www.nuget.org/packages/Sundstrom.CheckedExceptions/) ![License](https://img.shields.io/badge/license-MIT-blue.svg)
6-
7-
[❓ FAQ](#user-content--frequently-asked-questions-faq)[🧪 Sample project](https://github.com/marinasundstrom/CheckedExceptions/blob/main/SampleProject/Program.cs)[📚 Documentation](docs)[📝 Change Log](CHANGELOG.md)
8-
9-
---
10-
11-
## Demo
12-
13-
_Click the image to watch the [video](https://www.youtube.com/watch?v=ldJjMrqB8X4) om YouTube._
14-
15-
<a href="https://www.youtube.com/watch?v=ldJjMrqB8X4"><img src="screenshots/Screenshot3.png" alt="Video" width="500" /></a>
16-
17-
There are other videos in [this playlist](https://www.youtube.com/playlist?list=PLLBU--06ftFpmZWhQExVDXcefWol1i0zq).
18-
19-
---
20-
21-
## 🚀 What It Does
22-
23-
CheckedExceptions is a Roslyn analyzer that makes exception handling **explicit** and reveals how exceptions propagate through your code.
24-
25-
If a method might throw an exception, the caller must either:
26-
27-
* 🧯 Handle it (with `try/catch`), or
28-
* 📣 Declare it (with `[Throws(typeof(...))]`)
29-
30-
✅ Inspired by Java’s checked exceptions<br />
31-
⚙️ Fully opt-in<br />
32-
💡 Analyzer warnings by default — can be elevated to errors<br />
33-
📄 Supports .NET and third-party libraries via XML documentation<br />
34-
🛠 Includes code fixes to help you quickly handle or declare exceptions<br />
35-
➕ Supports .NET Standard 2.1<br />
36-
37-
---
38-
39-
## ✅ Quick Example
5+
```bash
6+
dotnet add package Sundstrom.CheckedExceptions
7+
```
408

419
```csharp
4210
public class Sample
4311
{
4412
public void Execute()
4513
{
46-
// ⚠️ THROW001: Unhandled exception type 'InvalidOperationException'
47-
Perform();
14+
Perform(); // ⚠️ THROW001: Unhandled InvalidOperationException
4815
}
4916

5017
[Throws(typeof(InvalidOperationException))]
51-
public void Perform()
52-
{
53-
throw new InvalidOperationException("Oops!");
54-
}
55-
}
56-
```
57-
58-
✔️ Fix it by **handling**:
59-
60-
```csharp
61-
public void Execute()
62-
{
63-
try { Perform(); }
64-
catch (InvalidOperationException) { /* handle */ }
65-
}
66-
```
67-
68-
Or by **declaring**:
69-
70-
```csharp
71-
[Throws(typeof(InvalidOperationException))]
72-
public void Execute()
73-
{
74-
Perform();
75-
}
76-
```
77-
78-
---
79-
80-
## 🧠 Why Use It?
81-
82-
- Avoid silent exception propagation
83-
- Document intent with `[Throws]` instead of comments
84-
- Enforce better error design across your codebase
85-
- Works with unannotated .NET methods via XML docs
86-
- Plays nice with nullable annotations
87-
- Avoid confusing [Throws] with `<exception>` — enforce contracts, not just documentation
88-
89-
---
90-
91-
## 📦 Installation
92-
93-
```bash
94-
dotnet add package Sundstrom.CheckedExceptions
95-
```
96-
97-
And define `ThrowsAttribute` in your project:
98-
99-
```csharp
100-
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Delegate | AttributeTargets.Property, AllowMultiple = true)]
101-
public class ThrowsAttribute : Attribute
102-
{
103-
public List<Type> ExceptionTypes { get; } = new();
104-
public ThrowsAttribute(Type exceptionType, params Type[] others) { … }
105-
}
106-
```
107-
108-
Find the full definition [here](https://github.com/marinasundstrom/CheckedExceptions/blob/main/CheckedExceptions.Attribute/ThrowsAttribute.cs).
109-
110-
---
111-
112-
## ⚙️ Configuration
113-
114-
### .editorconfig
115-
116-
```ini
117-
dotnet_diagnostic.THROW001.severity = warning
118-
dotnet_diagnostic.THROW003.severity = warning
119-
```
120-
121-
### `.csproj`
122-
123-
```xml
124-
<PropertyGroup>
125-
<WarningsAsErrors>nullable,THROW001</WarningsAsErrors>
126-
</PropertyGroup>
127-
```
128-
129-
### JSON Settings
130-
131-
Add `CheckedExceptions.settings.json`:
132-
133-
```json
134-
{
135-
// Exceptions to completely ignore during analysis (Glob pattern).
136-
"ignoredExceptions": [
137-
"System.*",
138-
"System.ArgumentNullException",
139-
"!System.InvalidOperationException"
140-
],
141-
142-
// Exceptions to ignore but still report as informational diagnostics.
143-
"informationalExceptions": {
144-
"System.IO.IOException": "Propagation",
145-
"System.TimeoutException": "Always"
146-
},
147-
148-
// If true, exceptions will not be read from XML documentation (default: false).
149-
"disableXmlDocInterop": false,
150-
151-
// If true, analysis on LINQ constructs will be disabled (default: false).
152-
"disableLinqSupport": false,
153-
154-
// If true, exceptions in LINQ lambdas do not have to be declared (default: false).
155-
"disableLinqImplicitlyDeclaredExceptions": false,
156-
157-
// If true, no diagnostics will be issued on contract boundaries, such as arguments to methods and return statements (default: false).
158-
"disableLinqEnumerableBoundaryWarnings": false,
159-
160-
// If true, control flow analysis, with redundancy checks, is disabled (default: false).
161-
"disableControlFlowAnalysis": false,
162-
163-
// If true, basic redundancy checks are available when control flow analysis is disabled (default: false).
164-
"enableLegacyRedundancyChecks": false,
165-
166-
// If true, the analyzer will not warn about declaring base type Exception with [Throws] (default: false).
167-
"disableBaseExceptionDeclaredDiagnostic": false,
168-
169-
// If true, the analyzer will not warn about throwing base type Exception (default: false).
170-
// Set to true if you use another analyzer reporting a similar diagnostic.
171-
"disableBaseExceptionThrownDiagnostic": false
18+
public void Perform() => throw new InvalidOperationException();
17219
}
17320
```
17421

175-
> **Control flow analysis** powers redundancy checks (e.g. unreachable code, redundant catches, unused exception declarations).
176-
> Disabling it may improve analyzer performance slightly at the cost of precision.
177-
178-
Register in `.csproj`:
179-
180-
```xml
181-
<ItemGroup>
182-
<AdditionalFiles Include="CheckedExceptions.settings.json" />
183-
</ItemGroup>
184-
```
185-
186-
---
187-
188-
## 🔍 Diagnostics
189-
190-
| ID | Message |
191-
|------------|-------------------------------------------------------------------------|
192-
| `THROW001` | ❗ Unhandled exception: must be caught or declared |
193-
| `THROW002` | ℹ️ Ignored exception may cause runtime issues |
194-
| `THROW003` | 🚫 Avoid declaring exception type `System.Exception` |
195-
| `THROW004` | 🚫 Avoid throwing exception base type `System.Exception` |
196-
| `THROW005` | 🔁 Duplicate declarations of the same exception type in `[Throws]` |
197-
| `THROW006` | 🧬 Incompatible Throws declaration (not declared on base member) |
198-
| `THROW007` | 🧬 Missing Throws declaration for base member's exception |
199-
| `THROW008` | 📦 Exception already handled by declaration of super type in `[Throws]` |
200-
| `THROW009` | 🧹 Redundant catch typed clause |
201-
| `THROW010` | ⚠️ `[Throws]` is not valid on full property declarations |
202-
| `THROW011` | 📄 Exception in XML docs is not declared with `[Throws]` |
203-
| `THROW012` | 🧹 Redundant exception declaration (declared but never thrown) |
204-
| `THROW013` | 🧹 Redundant catch-all clause (no remaining exceptions to catch) |
205-
| `THROW014` | 🧹 Catch clause has no remaining exceptions to handle |
206-
| `THROW015` | 🧹 Catch clause is redundant (General diagnostic) |
207-
| `THROW020` | 🛑 Unreachable code detected |
208-
| `IDE001` | 🙈 Unreachable code (hidden diagnostic for editor greying) |
209-
210-
## 🛠 Code Fixes
211-
212-
The analyzer offers the following automated code fixes:
213-
214-
***Add exception declaration**
215-
Adds `[Throws(typeof(...))]` attribute to declare the exception, or appends exception to existing attribute.
216-
*(Fixes `THROW001`)*
217-
218-
* 🧯 **Surround with try/catch**
219-
Wraps the throwing site in a `try`/`catch` block.
220-
*(Fixes `THROW001`)*
221-
222-
***Add catch to existing try block**
223-
Appends a new `catch` clause to an existing `try`.
224-
*(Fixes `THROW001`)*
225-
226-
* 🧹 **Remove redundant catch clause**
227-
Removes a catch clause for an exception type that is never thrown.
228-
*(Fixes `THROW009`, `THROW013`, `THROW014`)*
229-
230-
* 🔧 **Add `[Throws]` declaration from base member**
231-
Ensures overridden or implemented members declare the same exceptions as their base/interface.
232-
*(Fixes `THROW007`)*
233-
234-
* 🔧 **Add `[Throws]` declaration from XML doc**
235-
Converts `<exception>` XML documentation into `[Throws]` attributes.
236-
*(Fixes `THROW011`)*
237-
238-
***Introduce catch clauses from rethrow in catch-all**
239-
Appends new `catch` clauses before "catch all".
240-
*(Fixes `THROW001`)*
241-
242-
---
243-
244-
## ✨ Advanced Features
245-
246-
* **Lambdas, local functions, accessors, events** – full support across member kinds
247-
* **Exception inheritance analysis** – understands base/derived exception relationships
248-
* **XML documentation interop** – merges `[Throws]` with `<exception>` tags from external libraries
249-
* **Nullability awareness** – respects `#nullable enable` context
250-
* **Standard library knowledge** – recognizes common framework exceptions (e.g. `Console.WriteLine``IOException`)
251-
* **Control flow analysis** – detects whether exceptions are reachable, flags redundant `catch` clauses, and reports unreachable code caused by prior throws or returns
252-
253-
---
254-
255-
## ❓ Frequently Asked Questions (FAQ)
256-
257-
### ❓ How is this different from Java's checked exceptions?
258-
259-
**Answer:**
260-
261-
Java's checked exceptions are **mandatory** — the compiler enforces them, and every method must declare or handle them. While this promotes visibility, it also leads to friction, boilerplate, and workarounds like `throws Exception`.
262-
263-
This analyzer takes a **modern, flexible approach**:
264-
265-
* ⚠️ **Warnings by default**, not errors — you’re in control.
266-
* ✍️ **Opt-in declaration** using `[Throws]` — only where it matters.
267-
* 🛠️ **Code fixes and suppression** make adoption practical.
268-
* 🔄 **Gradual adoption** — use it for new code, leave legacy code untouched.
269-
* 🎯 **Focused on intention**, not obligation — you declare what callers need to know, not what `int.Parse` might throw.
270-
271-
> ✅ Summary:
272-
> This is *exception design with intent*, not enforcement by force. It improves exception hygiene without the rigidity of Java’s model.
273-
274-
### ❓ Can I use `<exception>` XML documentation tags instead of the `[Throws]` attribute?
275-
276-
**Answer:**
277-
278-
No — for your own code, `<exception>` tags are **not treated as semantic declarations** by the analyzer. While they are useful for documentation and IntelliSense, they are not part of the C# language’s type system and cannot be reliably analyzed or enforced.
279-
280-
Instead, we encourage and require the use of the `[Throws]` attribute for declaring exceptions in a way that is:
281-
282-
- Explicit and machine-readable
283-
- Suitable for static analysis and enforcement
284-
- Integrated with code fixes and tooling support
285-
286-
#### 🧩 Interoperability with external libraries
287-
288-
When analyzing external APIs (e.g., referenced .NET assemblies), we **do** recognize `<exception>` tags from their XML documentation — but only for **interop purposes**. That is:
289-
290-
- We treat documented exceptions from public APIs as "declared" when `[Throws]` is not available.
291-
- This helps maintain compatibility without requiring upstream changes.
292-
293-
> ⚠️ Summary:
294-
> `<exception>` tags are respected for **interop**, but they are **not a replacement** for `[Throws]` in code you control.
295-
296-
### ❓ What about .NET Standard 2.0 support?
297-
298-
**Answer:**
299-
300-
The analyzer offers **limited support** for projects targeting .NET Standard 2.0. You’ll still get accurate diagnostics for your own code, as well as third-party libraries. However, members defined in the .NET Standard framework may not indicate which exceptions they throw.
301-
302-
This is due to a **technical limitation**: the XML documentation files for .NET Standard assemblies are often incomplete or malformed, making it impossible to extract reliable exception information.
303-
304-
**Recommendation:** Target a modern .NET SDK (e.g., .NET 6 or later) to get full analyzer support, including framework exception annotations.
305-
306-
### ❓ What about LINQ support?
307-
308-
**Answer:**
309-
310-
There is initial support for LINQ query operators.
311-
312-
```csharp
313-
List<string> values = [ "10", "20", "abc", "30" ];
314-
315-
var tens = values
316-
.Where(s => int.Parse(s) % 10 is 0)
317-
.ToArray(); // THROW001: unhandled FormatException, OverflowException
318-
```
319-
320-
> Exceptions are inferred and implicit on LINQ methods, so no declarations needed. this behavior can be disabled.
321-
322-
Read about it [here](docs/linq-support.md).
323-
324-
---
325-
326-
## 🤝 Contributing
327-
328-
1. Fork
329-
2. Create feature branch
330-
3. Push PR with tests & documentation
331-
4. ❤️
332-
333-
---
334-
335-
## 📜 License
22+
For full documentation, samples, and FAQ, see the [project repository](https://github.com/marinasundstrom/CheckedExceptions).
33623

337-
[MIT](LICENSE)

CheckedExceptions.Tests/CheckedExceptions.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
<PackageReference Include="Microsoft.NET.Test.Sdk" />
1818
<PackageReference Include="xunit" />
1919
<PackageReference Include="xunit.runner.visualstudio" />
20+
<PackageReference Include="System.Linq.Async" />
2021
</ItemGroup>
2122

2223
<ItemGroup>

0 commit comments

Comments
 (0)