-
Notifications
You must be signed in to change notification settings - Fork 2.1k
feat(csharp): adds a missed first or default opprtunity rule #22485
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
baywet
wants to merge
18
commits into
github:main
Choose a base branch
from
baywet:feat/csharp-missed-firstordefault-opprtunity
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
ed15edf
feat(csharp): adds a missed first or default opprtunity rule
baywet 41a9a16
fix: handle non null default values in the rule
baywet baab759
chore: applies review suggestion
baywet a52c2cb
chore: applies review suggestion
baywet 6744d56
chore: reverts addition to ruleset
baywet c97f0f4
chore: applies review suggestions
baywet 6525503
chore: linting
baywet 543ead4
C#: Add change-note.
michaelnebel 2eb16da
C#: Update integration test expected output.
michaelnebel 6d867e1
C#: Address some review comments.
michaelnebel 1617ace
chore: applies review suggestions
baywet 63a5e8a
chore: applies review suggestions
baywet 9d48ed0
Merge main into feat/csharp-missed-firstordefault-opprtunity
baywet 6da0e6a
chore: applies review suggestions
baywet c006526
Merge branch 'main' into feat/csharp-missed-firstordefault-opprtunity
baywet 25aef0e
chore: formatting
baywet 8234bad
tests: adds additional validation for lambda corner case
baywet d9b1b6e
Merge branch 'feat/csharp-missed-firstordefault-opprtunity' of https:…
baywet File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
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
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
|
|
||
| class MissedFirstOrDefaultOpportunity | ||
| { | ||
| public static Operation FindOperation(IEnumerable<Operation> operations, string operationId) | ||
| { | ||
| foreach (var operation in operations) | ||
| { | ||
| if (string.Equals(operation.OperationId, operationId, StringComparison.Ordinal)) | ||
| return operation; | ||
| } | ||
|
|
||
| return null; | ||
| } | ||
| } | ||
|
|
||
| class Operation | ||
| { | ||
| public string OperationId { get; set; } | ||
| } |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| <!DOCTYPE qhelp PUBLIC | ||
| "-//Semmle//qhelp//EN" | ||
| "qhelp.dtd"> | ||
| <qhelp> | ||
| <overview> | ||
| <p>Programmers sometimes search a sequence by iterating over each element, testing it, and returning | ||
| the first element that satisfies the test. If the loop completes without finding a match, the method | ||
| then returns a default value such as <code>null</code> or <code>default</code>.</p> | ||
|
|
||
| </overview> | ||
| <recommendation> | ||
| <p>This pattern is directly available as the <code>FirstOrDefault</code> method in LINQ. Using the | ||
| library method makes the search intent explicit and avoids manually spelling out the loop and | ||
| fallback return.</p> | ||
|
|
||
| </recommendation> | ||
| <example> | ||
| <p>In this example the method searches a list of operations for the first operation with a matching | ||
| identifier, returning <code>null</code> if no match is found.</p> | ||
| <sample src="MissedFirstOrDefaultOpportunity.cs" /> | ||
|
|
||
| <p>The LINQ <code>FirstOrDefault</code> method can express this search more directly.</p> | ||
| <sample src="MissedFirstOrDefaultOpportunityFix.cs" /> | ||
|
|
||
| <p>The following examples should not use <code>FirstOrDefault</code>, because they do more than | ||
| return the matching element or because the fallback value is not the default value.</p> | ||
| <sample src="MissedFirstOrDefaultOpportunityGood.cs" /> | ||
|
|
||
| </example> | ||
| <references> | ||
|
|
||
| <li>MSDN: <a href="https://learn.microsoft.com/dotnet/api/system.linq.enumerable.firstordefault">Enumerable.FirstOrDefault Method</a>.</li> | ||
|
|
||
|
|
||
| </references> | ||
| </qhelp> |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| /** | ||
| * @name Missed opportunity to use FirstOrDefault | ||
| * @description The intent of a foreach loop that returns the first sequence element satisfying a predicate, or a default value otherwise, | ||
| * can often be better expressed using LINQ's 'FirstOrDefault' method. | ||
| * @kind problem | ||
| * @problem.severity recommendation | ||
| * @precision high | ||
| * @id cs/linq/missed-firstordefault | ||
| * @tags quality | ||
| * maintainability | ||
| * readability | ||
| * language-features | ||
| */ | ||
|
|
||
| import csharp | ||
| import Linq.Helpers | ||
|
|
||
| from ForeachStmtGenericEnumerable fes, IfStmt is | ||
| where missedFirstOrDefaultOpportunity(fes, is) | ||
|
baywet marked this conversation as resolved.
|
||
| select fes, | ||
| "This foreach loop returns the first sequence element satisfying a $@ - consider finding the element explicitly using '.FirstOrDefault(...)'.", | ||
| is.getCondition(), "predicate" | ||
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Linq; | ||
|
|
||
| class MissedFirstOrDefaultOpportunityFix | ||
| { | ||
| public static Operation FindOperation(IEnumerable<Operation> operations, string operationId) | ||
| { | ||
| return operations.FirstOrDefault(operation => | ||
| string.Equals(operation.OperationId, operationId, StringComparison.Ordinal)); | ||
| } | ||
| } |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
|
|
||
| class MissedFirstOrDefaultOpportunityGood | ||
| { | ||
| public static Operation FindOperationOrThrow(IEnumerable<Operation> operations, string operationId) | ||
| { | ||
| foreach (var operation in operations) | ||
| { | ||
| if (string.Equals(operation.OperationId, operationId, StringComparison.Ordinal)) | ||
| throw new InvalidOperationException("Unexpected operation."); | ||
| } | ||
|
|
||
| return null; | ||
| } | ||
|
|
||
| public static Operation FindReplacementOperation(IEnumerable<Operation> operations, string operationId) | ||
| { | ||
| foreach (var operation in operations) | ||
| { | ||
| if (string.Equals(operation.OperationId, operationId, StringComparison.Ordinal)) | ||
| return operation; | ||
| } | ||
|
|
||
| return new Operation(); | ||
| } | ||
|
|
||
| public static string FindOperationId(IEnumerable<Operation> operations, string operationId) | ||
| { | ||
| foreach (var operation in operations) | ||
| { | ||
| if (string.Equals(operation.OperationId, operationId, StringComparison.Ordinal)) | ||
| return operation.OperationId; | ||
| } | ||
|
|
||
| return null; | ||
| } | ||
| } |
4 changes: 4 additions & 0 deletions
4
csharp/ql/src/change-notes/2026-09-03-missed-firstordefault.md
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| --- | ||
| category: newQuery | ||
| --- | ||
| * Added a new query, `cs/linq/missed-firstordefault`, that detects `foreach` loops that can be expressed more clearly using LINQ's `FirstOrDefault` method. |
Oops, something went wrong.
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.