Repository navigation
Add analyzer λ1012: prefer the …OrNone method over …OrDefault with a null check - #973
Merged
Merged
Conversation
…null check
FirstOrDefault, LastOrDefault, SingleOrDefault and ElementAtOrDefault whose
result is immediately tested for null (??, ?., is null, is { } x, == null,
is T t, …) are reported; the …OrNone variants return an Option instead.
Only sequences of a non-nullable reference type are considered, the
overloads with an explicit default value and IQueryable are left alone.
The code fix rewrites `xs.FirstOrDefault() ?? fallback` to
`xs.FirstOrNone().GetOrElse(fallback)` (a throw fallback becomes lazy) and
adds `using Funcky.Extensions;` next to the existing usings when the name
does not bind yet. The other shapes have no single idiomatic replacement
and are reported without a fix.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
3 tasks done
… usings Keeps the helper identical to the one in the λ1018 branch. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
The analyzer misses intended nullable-aware cases, and code fixes can change behavior or produce compilation errors.
5 open issues
Avoid copying preprocessor directives into generated using directives · New Preserve explicit generic arguments when renaming the call · New Validate result type compatibility before offering the rewrite · New Preserve lazy evaluation for non-throwing coalescing fallbacks · New Check LINQ method type arguments instead of return type · New
What changed in this PR
Adds λ1012 to encourage using Funcky’s option-returning LINQ methods instead of checking …OrDefault results for null.
Changes:
- Adds null-check detection and a code fix for
??expressions. - Adds import insertion support and analyzer/code-fix tests.
- Documents the rule and registers it in release notes.
| File | Description |
|---|---|
| Funcky.Analyzers/Funcky.Analyzers/OrNone/PreferOrNoneAnalyzer.cs | Implements λ1012 detection. |
| Funcky.Analyzers/Funcky.Analyzers/AnalyzerReleases.Unshipped.md | Registers the warning. |
| Funcky.Analyzers/Funcky.Analyzers.Test/OrNone/PreferOrNoneTest.cs | Tests diagnostics and rewrites. |
| Funcky.Analyzers/Funcky.Analyzers.CodeFixes/OrNone/PreferOrNoneCodeFix.cs | Rewrites coalescing expressions. |
| Funcky.Analyzers/Funcky.Analyzers.CodeFixes/CodeAnalysisExtensions/UsingDirectives.cs | Adds extension namespace imports. |
| Documentation/src/SUMMARY.md | Links the rule documentation. |
| Documentation/src/analyzer-rules/λ1012.md | Explains usage and replacements. |
| Documentation/src/analyzer-rules/analyzer-rules.md | Adds the rule to the index. |
| changelog.md | Announces the analyzer and fix. |
Review effort: Balanced
… and decline widened types - The analyzer checks the element type argument instead of the return type, which the nullable-annotated LINQ always annotates, and uses the Funcky.Style category. - The fix keeps explicit type arguments when renaming, offers nothing when the `??` has a wider type than the element, passes a fallback that does work as a lambda (declining when it cannot move into one) and inserts the using outside a conditional region with formatting trivia only. The directive-safe insertion is shared with the λ1017 fix. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…y, and behind leading #define directives The directive-safe insertion of the λ1017 fix moves into the shared UsingDirectives helper, which copies only whitespace and line endings from its neighbour. In a file without usings a leading `#define` or `#undef` moves in front of the new using, since those directives must precede every token. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
JudoJosef
approved these changes
Oct 20, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
FirstOrDefault,LastOrDefault,SingleOrDefaultandElementAtOrDefaultwhose result is immediately tested fornull(?? fallback,?.,is null/is not null,== null/!= null,is { } x,is not { } x,is T t). The…OrNonevariants make the missing element aNoneinstead of anullevery caller has to check by hand.IEnumerable<string?>anullresult is ambiguous between "no element" and "the element is null". The overloads with an explicit default value, the static call form andIQueryable(whereFirstOrDefaulttranslates to the query) are left alone. The rule is silent when the…OrNonemethod is not available.xs.FirstOrDefault() ?? fallbackbecomesxs.FirstOrNone().GetOrElse(fallback); athrowfallback becomes the lazyGetOrElse(() => throw …). It addsusing Funcky.Extensions;next to the existing usings (file level or inside the namespace) when the method does not bind yet. The other null-check shapes are reported without a fix, the rule doc lists the idiomatic replacement for each.UsingDirectives.AddUsingin the code fix project (parsed directive, trivia copied from the last existing using, so no CRLF/LF normalization).λ1012.md, rule table, SUMMARY, changelog, AnalyzerReleases.Unshipped.Note for reviewers: the operation tree presents an extension call unreduced (
TargetMethodis the static method with the receiver as first argument), so the matcher checksIsExtensionMethodand derives the extension-call form from the syntax.Test plan
PreferOrNoneTest: fix with using added at file level and inside a namespace, every method/overload incl. predicate, index, parentheses and throw fallback, the no-fix shapes, no null check, value types / nullable elements /IQueryable/ static form not reporteddotnet build Funcky.slnx -warnaserror --no-incrementalgreen (no hits in Funcky itself)🤖 Generated with Claude Code