Fix BL0012 false positive on await foreach and await using (#68720)
* Fix BL0012 false positive on await foreach and await using
StateHasChangedAnalyzer located the first and last await by collecting
AwaitExpressionSyntax nodes. `await foreach`, `await using (...) { }` and
`await using var x = ...;` carry their await as a keyword token on the statement, so a
method whose only awaits take one of those forms looked await-free and every
StateHasChanged in it was reported as redundant.
Track the region the method can suspend in instead, taking the statement span for
`await foreach` and for the block form of `await using`, and the enclosing block for a
using declaration, whose disposal runs when that block exits. Calls outside that region
are still reported, so behaviour for ordinary awaits is unchanged.
Found while validating #68484.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018irsMDStKQdmB1k7fkUmqM
* Treat await using disposal as the suspension point
The block form took the whole statement span as the suspension region, so a
StateHasChanged in the body looked like it sat between awaits. Acquisition is
synchronous and only the DisposeAsync suspends, so the body still runs before
the first suspension and the call is redundant. The using declaration form had
the same defect, with disposal happening when the enclosing block exits.
Both now use the disposal point. await foreach keeps the whole-statement span:
MoveNextAsync is awaited before each iteration, so its body is genuinely inside
the suspension region.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012eF853sQZryRDVkKMgZb4Q
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> M
Muhammad Azeem committed
9cf09cfcb2ba03704490c934dec75e12c09a8e46
Parent: de3b5fb
Committed by GitHub <noreply@github.com>
on 9/9/2026, 11:18:09 AM