-
Notifications
You must be signed in to change notification settings - Fork 4
Optimize DefaultIfNotSingle loop #430
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
Merged
rjmurillo
merged 30 commits into
main
from
codex/refactor-loop-to-replace-where-predicate
Jun 9, 2025
Merged
Changes from all commits
Commits
Show all changes
30 commits
Select commit
Hold shift + click to select a range
c3f56c5
Add benchmarks for DefaultIfNotSingle variants
rjmurillo 2993c6c
Fix: Ensure DefaultIfNotSingle optimizations and tests pass all local…
rjmurillo 79a49d1
Add null checks to DefaultIfNotSingleOptimizedMethod using ArgumentNu…
rjmurillo 4909c58
Rebaseline SquiggleCop
rjmurillo 3de2741
Use ArgumentNullException.ThrowIfNull for parameter validation in Def…
rjmurillo 128aebf
Revert to classic null-check pattern in DefaultIfNotSingle extension …
rjmurillo bf4a05b
Update SquiggleCop.Baseline.yaml to set IsEverSuppressed to true for …
rjmurillo 9b2502f
Add tests to cover ArgumentNullException for null source and predicat…
rjmurillo ad58295
fix(test): reset CountingEnumerable<T>.Count at start of each enumera…
rjmurillo 5f03f64
test: add missing usings and use explicit types to fix ECS0900 and fo…
rjmurillo fc0ed86
test: use int? and Assert.Null for DefaultIfNotSingle results to fix …
rjmurillo 1284a4d
test: add ECS0900-compliant string-based tests for DefaultIfNotSingle…
rjmurillo f78c22f
test: remove int-based DefaultIfNotSingle null predicate tests to res…
rjmurillo 7cccbda
test: remove int-based null source test for DefaultIfNotSingle to res…
rjmurillo 972bf0c
test: expect 0 (default int) instead of null for DefaultIfNotSingle<i…
rjmurillo 0990b09
test: update DefaultIfNotSingle test to expect null for empty object …
rjmurillo 669f446
chore: update SquiggleCop.Baseline.yaml and test baseline to change E…
rjmurillo 65ff0e0
test: add CountingEnumerable_Count test to verify Count resets on eac…
rjmurillo 7c6fa95
chore: update SquiggleCop.Baseline.yaml and test baseline to change E…
rjmurillo ae2f96a
refactor: extract argument validation into a separate method for Defa…
rjmurillo 994abca
Update tests/Moq.Analyzers.Test/SquiggleCop.Baseline.yaml
rjmurillo 7c97fea
Resolve merge: accept deletion of SquiggleCop.Baseline.yaml files as …
rjmurillo 14c44f8
Merge branch 'codex/refactor-loop-to-replace-where-predicate' of http…
rjmurillo 68d8648
test: add tests for DefaultIfNotSingle method to verify behavior with…
rjmurillo 6df8c51
test: cover early returns in SetStrictMockBehaviorFixer.RegisterCodeF…
rjmurillo b433321
test: add DiagnosticEditPropertiesTests to cover all branches and edg…
rjmurillo caecc7a
refactor: remove untestable early return tests from SetExplicitMockBe…
rjmurillo a8eb22b
test: enhance DefaultIfNotSingle tests to assert parameter name in Ar…
rjmurillo ec8564d
Merge branch 'main' into codex/refactor-loop-to-replace-where-predicate
rjmurillo b197af1
Merge branch 'main' into codex/refactor-loop-to-replace-where-predicate
rjmurillo 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
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
36 changes: 36 additions & 0 deletions
36
tests/Moq.Analyzers.Benchmarks/DefaultIfNotSingleBaseline.cs
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 @@ | ||
| using System; | ||
| using System.Collections.Immutable; | ||
| using System.Linq; | ||
|
|
||
| namespace Moq.Analyzers.Benchmarks; | ||
|
|
||
| #pragma warning disable ECS0900 // Minimize boxing and unboxing | ||
| internal static class DefaultIfNotSingleBaseline | ||
| { | ||
| public static T? DefaultIfNotSingleBaselineMethod<T>(this IEnumerable<T> source, Func<T, bool> predicate) | ||
| { | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| ArgumentNullException.ThrowIfNull(source); | ||
| ArgumentNullException.ThrowIfNull(predicate); | ||
| bool found = false; | ||
| T? item = default; | ||
| foreach (T element in source.Where(predicate)) | ||
| { | ||
| if (found) | ||
| { | ||
| return default; | ||
| } | ||
|
|
||
| found = true; | ||
| item = element; | ||
| } | ||
|
|
||
| return item; | ||
| } | ||
|
|
||
| public static T? DefaultIfNotSingleBaselineMethod<T>(this IEnumerable<T> source) | ||
| => source.DefaultIfNotSingleBaselineMethod(static _ => true); | ||
|
|
||
| public static T? DefaultIfNotSingleBaselineMethod<T>(this ImmutableArray<T> source, Func<T, bool> predicate) | ||
| => source.AsEnumerable().DefaultIfNotSingleBaselineMethod(predicate); | ||
| #pragma warning restore ECS0900 | ||
| } | ||
17 changes: 17 additions & 0 deletions
17
tests/Moq.Analyzers.Benchmarks/DefaultIfNotSingleEnumerableBenchmarks.cs
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,17 @@ | ||
| using System.Linq; | ||
| using BenchmarkDotNet.Attributes; | ||
|
|
||
| namespace Moq.Analyzers.Benchmarks; | ||
|
|
||
| [MemoryDiagnoser] | ||
| [InProcess] | ||
| public class DefaultIfNotSingleEnumerableBenchmarks | ||
| { | ||
| private readonly IEnumerable<int> _source = Enumerable.Range(0, 100).ToArray(); | ||
|
|
||
| [Benchmark(Baseline = true)] | ||
| public int? Baseline() => _source.DefaultIfNotSingleBaselineMethod(x => x == 50); | ||
|
|
||
| [Benchmark] | ||
| public int? Optimized() => _source.DefaultIfNotSingleOptimizedMethod(x => x == 50); | ||
| } |
17 changes: 17 additions & 0 deletions
17
tests/Moq.Analyzers.Benchmarks/DefaultIfNotSingleEnumerableNoPredicateBenchmarks.cs
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,17 @@ | ||
| using System.Linq; | ||
| using BenchmarkDotNet.Attributes; | ||
|
|
||
| namespace Moq.Analyzers.Benchmarks; | ||
|
|
||
| [MemoryDiagnoser] | ||
| [InProcess] | ||
| public class DefaultIfNotSingleEnumerableNoPredicateBenchmarks | ||
| { | ||
| private readonly IEnumerable<int> _source = new[] { 0 }; | ||
|
|
||
| [Benchmark(Baseline = true)] | ||
| public int? Baseline() => _source.DefaultIfNotSingleBaselineMethod(); | ||
|
|
||
| [Benchmark] | ||
| public int? Optimized() => _source.DefaultIfNotSingleOptimizedMethod(); | ||
| } |
20 changes: 20 additions & 0 deletions
20
tests/Moq.Analyzers.Benchmarks/DefaultIfNotSingleImmutableArrayBenchmarks.cs
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,20 @@ | ||
| using System.Collections.Immutable; | ||
| using System.Linq; | ||
| using BenchmarkDotNet.Attributes; | ||
|
|
||
| namespace Moq.Analyzers.Benchmarks; | ||
|
|
||
| [MemoryDiagnoser] | ||
| [InProcess] | ||
| public class DefaultIfNotSingleImmutableArrayBenchmarks | ||
| { | ||
| private readonly ImmutableArray<int> _source = ImmutableArray.CreateRange(Enumerable.Range(0, 100)); | ||
|
|
||
| #pragma warning disable ECS0900 // Minimize boxing and unboxing | ||
| [Benchmark(Baseline = true)] | ||
| public int? Baseline() => _source.DefaultIfNotSingleBaselineMethod(x => x == 50); | ||
|
|
||
| [Benchmark] | ||
| public int? Optimized() => _source.DefaultIfNotSingleOptimizedMethod(x => x == 50); | ||
| #pragma warning restore ECS0900 | ||
| } |
41 changes: 41 additions & 0 deletions
41
tests/Moq.Analyzers.Benchmarks/DefaultIfNotSingleOptimized.cs
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,41 @@ | ||
| using System; | ||
| using System.Collections.Immutable; | ||
| using System.Linq; | ||
|
|
||
| namespace Moq.Analyzers.Benchmarks; | ||
|
|
||
| #pragma warning disable ECS0900 // Minimize boxing and unboxing | ||
| internal static class DefaultIfNotSingleOptimized | ||
| { | ||
| public static T? DefaultIfNotSingleOptimizedMethod<T>(this IEnumerable<T> source, Func<T, bool> predicate) | ||
| { | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| ArgumentNullException.ThrowIfNull(source); | ||
| ArgumentNullException.ThrowIfNull(predicate); | ||
| bool found = false; | ||
| T? item = default; | ||
| foreach (T element in source) | ||
| { | ||
| if (!predicate(element)) | ||
| { | ||
| continue; | ||
| } | ||
|
|
||
| if (found) | ||
| { | ||
| return default; | ||
| } | ||
|
|
||
| found = true; | ||
| item = element; | ||
| } | ||
|
|
||
| return item; | ||
| } | ||
|
|
||
| public static T? DefaultIfNotSingleOptimizedMethod<T>(this IEnumerable<T> source) | ||
| => source.DefaultIfNotSingleOptimizedMethod(static _ => true); | ||
|
|
||
| public static T? DefaultIfNotSingleOptimizedMethod<T>(this ImmutableArray<T> source, Func<T, bool> predicate) | ||
| => source.AsEnumerable().DefaultIfNotSingleOptimizedMethod(predicate); | ||
| #pragma warning restore ECS0900 | ||
| } | ||
70 changes: 70 additions & 0 deletions
70
tests/Moq.Analyzers.Test/Common/DiagnosticEditPropertiesTests.cs
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,70 @@ | ||
| using System.Collections.Immutable; | ||
| using Moq.Analyzers.Common; | ||
| using Xunit; | ||
|
|
||
| namespace Moq.Analyzers.Test.Common; | ||
|
|
||
| public class DiagnosticEditPropertiesTests | ||
| { | ||
| [Fact] | ||
| public void ToImmutableDictionary_RoundTripsProperties() | ||
| { | ||
| DiagnosticEditProperties props = new DiagnosticEditProperties { TypeOfEdit = DiagnosticEditProperties.EditType.Insert, EditPosition = 2 }; | ||
| ImmutableDictionary<string, string?> dict = props.ToImmutableDictionary(); | ||
| Assert.Equal("Insert", dict[DiagnosticEditProperties.EditTypeKey]); | ||
| Assert.Equal("2", dict[DiagnosticEditProperties.EditPositionKey]); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void TryGetFromImmutableDictionary_Succeeds_WithValidDictionary() | ||
| { | ||
| ImmutableDictionary<string, string?> dict = ImmutableDictionary<string, string?>.Empty | ||
| .Add(DiagnosticEditProperties.EditTypeKey, "Replace") | ||
| .Add(DiagnosticEditProperties.EditPositionKey, "5"); | ||
| bool result = DiagnosticEditProperties.TryGetFromImmutableDictionary(dict, out DiagnosticEditProperties? props); | ||
| Assert.True(result); | ||
| Assert.NotNull(props); | ||
| Assert.Equal(DiagnosticEditProperties.EditType.Replace, props!.TypeOfEdit); | ||
| Assert.Equal(5, props.EditPosition); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void TryGetFromImmutableDictionary_Fails_WhenEditTypeKeyMissing() | ||
| { | ||
| ImmutableDictionary<string, string?> dict = ImmutableDictionary<string, string?>.Empty.Add(DiagnosticEditProperties.EditPositionKey, "1"); | ||
| bool result = DiagnosticEditProperties.TryGetFromImmutableDictionary(dict, out DiagnosticEditProperties? props); | ||
| Assert.False(result); | ||
| Assert.Null(props); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void TryGetFromImmutableDictionary_Fails_WhenEditPositionKeyMissing() | ||
| { | ||
| ImmutableDictionary<string, string?> dict = ImmutableDictionary<string, string?>.Empty.Add(DiagnosticEditProperties.EditTypeKey, "Insert"); | ||
| bool result = DiagnosticEditProperties.TryGetFromImmutableDictionary(dict, out DiagnosticEditProperties? props); | ||
| Assert.False(result); | ||
| Assert.Null(props); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void TryGetFromImmutableDictionary_Fails_WhenEditTypeInvalid() | ||
| { | ||
| ImmutableDictionary<string, string?> dict = ImmutableDictionary<string, string?>.Empty | ||
| .Add(DiagnosticEditProperties.EditTypeKey, "NotAType") | ||
| .Add(DiagnosticEditProperties.EditPositionKey, "1"); | ||
| bool result = DiagnosticEditProperties.TryGetFromImmutableDictionary(dict, out DiagnosticEditProperties? props); | ||
| Assert.False(result); | ||
| Assert.Null(props); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void TryGetFromImmutableDictionary_Fails_WhenEditPositionInvalid() | ||
| { | ||
| ImmutableDictionary<string, string?> dict = ImmutableDictionary<string, string?>.Empty | ||
| .Add(DiagnosticEditProperties.EditTypeKey, "Insert") | ||
| .Add(DiagnosticEditProperties.EditPositionKey, "notAnInt"); | ||
| bool result = DiagnosticEditProperties.TryGetFromImmutableDictionary(dict, out DiagnosticEditProperties? props); | ||
| Assert.False(result); | ||
| Assert.Null(props); | ||
| } | ||
| } |
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.