Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
c3f56c5
Add benchmarks for DefaultIfNotSingle variants
rjmurillo Jun 1, 2025
2993c6c
Fix: Ensure DefaultIfNotSingle optimizations and tests pass all local…
rjmurillo Jun 8, 2025
79a49d1
Add null checks to DefaultIfNotSingleOptimizedMethod using ArgumentNu…
rjmurillo Jun 8, 2025
4909c58
Rebaseline SquiggleCop
rjmurillo Jun 8, 2025
3de2741
Use ArgumentNullException.ThrowIfNull for parameter validation in Def…
rjmurillo Jun 8, 2025
128aebf
Revert to classic null-check pattern in DefaultIfNotSingle extension …
rjmurillo Jun 8, 2025
bf4a05b
Update SquiggleCop.Baseline.yaml to set IsEverSuppressed to true for …
rjmurillo Jun 8, 2025
9b2502f
Add tests to cover ArgumentNullException for null source and predicat…
rjmurillo Jun 8, 2025
ad58295
fix(test): reset CountingEnumerable<T>.Count at start of each enumera…
rjmurillo Jun 8, 2025
5f03f64
test: add missing usings and use explicit types to fix ECS0900 and fo…
rjmurillo Jun 8, 2025
fc0ed86
test: use int? and Assert.Null for DefaultIfNotSingle results to fix …
rjmurillo Jun 8, 2025
1284a4d
test: add ECS0900-compliant string-based tests for DefaultIfNotSingle…
rjmurillo Jun 8, 2025
f78c22f
test: remove int-based DefaultIfNotSingle null predicate tests to res…
rjmurillo Jun 8, 2025
7cccbda
test: remove int-based null source test for DefaultIfNotSingle to res…
rjmurillo Jun 8, 2025
972bf0c
test: expect 0 (default int) instead of null for DefaultIfNotSingle<i…
rjmurillo Jun 8, 2025
0990b09
test: update DefaultIfNotSingle test to expect null for empty object …
rjmurillo Jun 8, 2025
669f446
chore: update SquiggleCop.Baseline.yaml and test baseline to change E…
rjmurillo Jun 8, 2025
65ff0e0
test: add CountingEnumerable_Count test to verify Count resets on eac…
rjmurillo Jun 8, 2025
7c6fa95
chore: update SquiggleCop.Baseline.yaml and test baseline to change E…
rjmurillo Jun 8, 2025
ae2f96a
refactor: extract argument validation into a separate method for Defa…
rjmurillo Jun 8, 2025
994abca
Update tests/Moq.Analyzers.Test/SquiggleCop.Baseline.yaml
rjmurillo Jun 8, 2025
7c97fea
Resolve merge: accept deletion of SquiggleCop.Baseline.yaml files as …
rjmurillo Jun 8, 2025
14c44f8
Merge branch 'codex/refactor-loop-to-replace-where-predicate' of http…
rjmurillo Jun 8, 2025
68d8648
test: add tests for DefaultIfNotSingle method to verify behavior with…
rjmurillo Jun 8, 2025
6df8c51
test: cover early returns in SetStrictMockBehaviorFixer.RegisterCodeF…
rjmurillo Jun 8, 2025
b433321
test: add DiagnosticEditPropertiesTests to cover all branches and edg…
rjmurillo Jun 8, 2025
caecc7a
refactor: remove untestable early return tests from SetExplicitMockBe…
rjmurillo Jun 8, 2025
a8eb22b
test: enhance DefaultIfNotSingle tests to assert parameter name in Ar…
rjmurillo Jun 8, 2025
ec8564d
Merge branch 'main' into codex/refactor-loop-to-replace-where-predicate
rjmurillo Jun 9, 2025
b197af1
Merge branch 'main' into codex/refactor-loop-to-replace-where-predicate
rjmurillo Jun 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Analyzers/SquiggleCop.Baseline.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@
- {Id: HAA0603, Title: Delegate allocation from a method group, Category: Performance, DefaultSeverity: Warning, IsEnabledByDefault: true, EffectiveSeverities: [Error], IsEverSuppressed: false}
- {Id: HAA0604, Title: Delegate allocation from a method group, Category: Performance, DefaultSeverity: Note, IsEnabledByDefault: true, EffectiveSeverities: [Note], IsEverSuppressed: false}
- {Id: IDE0004, Title: Remove Unnecessary Cast, Category: Style, DefaultSeverity: Note, IsEnabledByDefault: true, EffectiveSeverities: [Note], IsEverSuppressed: true}
- {Id: IDE0005, Title: Using directive is unnecessary., Category: Style, DefaultSeverity: Note, IsEnabledByDefault: true, EffectiveSeverities: [Note], IsEverSuppressed: false}
- {Id: IDE0005, Title: Using directive is unnecessary., Category: Style, DefaultSeverity: Note, IsEnabledByDefault: true, EffectiveSeverities: [Note], IsEverSuppressed: true}
- {Id: IDE0005_gen, Title: Using directive is unnecessary., Category: Style, DefaultSeverity: Note, IsEnabledByDefault: true, EffectiveSeverities: [Note], IsEverSuppressed: true}
- {Id: IDE0007, Title: Use implicit type, Category: Style, DefaultSeverity: Note, IsEnabledByDefault: true, EffectiveSeverities: [Note], IsEverSuppressed: false}
- {Id: IDE0008, Title: Use explicit type, Category: Style, DefaultSeverity: Note, IsEnabledByDefault: true, EffectiveSeverities: [Note], IsEverSuppressed: false}
Expand Down
25 changes: 23 additions & 2 deletions src/Common/EnumerableExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Diagnostics.CodeAnalysis;
using System;
Comment thread
rjmurillo marked this conversation as resolved.
using System.Diagnostics.CodeAnalysis;

namespace Moq.Analyzers.Common;

Expand All @@ -16,6 +17,11 @@ internal static class EnumerableExtensions
[SuppressMessage("Performance", "ECS0900:Minimize boxing and unboxing", Justification = "Should revisit. Suppressing for now to unblock refactor.")]
public static TSource? DefaultIfNotSingle<TSource>(this ImmutableArray<TSource> source, Func<TSource, bool> predicate)
{
if (predicate == null)
{
throw new ArgumentNullException(nameof(predicate));
}

return source.AsEnumerable().DefaultIfNotSingle(predicate);
}

Expand All @@ -34,11 +40,26 @@ internal static class EnumerableExtensions
/// </remarks>
public static TSource? DefaultIfNotSingle<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
}

if (predicate == null)
{
throw new ArgumentNullException(nameof(predicate));
}

bool isFound = false;
TSource? item = default;

foreach (TSource element in source.Where(predicate))
foreach (TSource element in source)
{
if (!predicate(element))
{
continue;
}

if (isFound)
{
// We already found an element, thus there's multiple matches; return default.
Expand Down
36 changes: 36 additions & 0 deletions tests/Moq.Analyzers.Benchmarks/DefaultIfNotSingleBaseline.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System;

Check failure on line 1 in tests/Moq.Analyzers.Benchmarks/DefaultIfNotSingleBaseline.cs

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

tests/Moq.Analyzers.Benchmarks/DefaultIfNotSingleBaseline.cs#L1

Provide an 'AssemblyVersion' attribute for assembly 'srcassembly.dll'.
using System.Collections.Immutable;
using System.Linq;

namespace Moq.Analyzers.Benchmarks;

#pragma warning disable ECS0900 // Minimize boxing and unboxing
internal static class DefaultIfNotSingleBaseline

Check warning on line 8 in tests/Moq.Analyzers.Benchmarks/DefaultIfNotSingleBaseline.cs

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

tests/Moq.Analyzers.Benchmarks/DefaultIfNotSingleBaseline.cs#L8

Types should not have members with visibility set higher than the type's visibility
{
public static T? DefaultIfNotSingleBaselineMethod<T>(this IEnumerable<T> source, Func<T, bool> predicate)
{
Comment thread
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
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System.Linq;

Check failure on line 1 in tests/Moq.Analyzers.Benchmarks/DefaultIfNotSingleEnumerableBenchmarks.cs

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

tests/Moq.Analyzers.Benchmarks/DefaultIfNotSingleEnumerableBenchmarks.cs#L1

Provide an 'AssemblyVersion' attribute for assembly 'srcassembly.dll'.
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);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System.Linq;

Check failure on line 1 in tests/Moq.Analyzers.Benchmarks/DefaultIfNotSingleEnumerableNoPredicateBenchmarks.cs

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

tests/Moq.Analyzers.Benchmarks/DefaultIfNotSingleEnumerableNoPredicateBenchmarks.cs#L1

Provide an 'AssemblyVersion' attribute for assembly 'srcassembly.dll'.
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();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System.Collections.Immutable;

Check failure on line 1 in tests/Moq.Analyzers.Benchmarks/DefaultIfNotSingleImmutableArrayBenchmarks.cs

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

tests/Moq.Analyzers.Benchmarks/DefaultIfNotSingleImmutableArrayBenchmarks.cs#L1

Provide an 'AssemblyVersion' attribute for assembly 'srcassembly.dll'.
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 tests/Moq.Analyzers.Benchmarks/DefaultIfNotSingleOptimized.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System;

Check failure on line 1 in tests/Moq.Analyzers.Benchmarks/DefaultIfNotSingleOptimized.cs

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

tests/Moq.Analyzers.Benchmarks/DefaultIfNotSingleOptimized.cs#L1

Provide an 'AssemblyVersion' attribute for assembly 'srcassembly.dll'.
using System.Collections.Immutable;
using System.Linq;

namespace Moq.Analyzers.Benchmarks;

#pragma warning disable ECS0900 // Minimize boxing and unboxing
internal static class DefaultIfNotSingleOptimized

Check warning on line 8 in tests/Moq.Analyzers.Benchmarks/DefaultIfNotSingleOptimized.cs

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

tests/Moq.Analyzers.Benchmarks/DefaultIfNotSingleOptimized.cs#L8

Types should not have members with visibility set higher than the type's visibility
{
public static T? DefaultIfNotSingleOptimizedMethod<T>(this IEnumerable<T> source, Func<T, bool> predicate)
{
Comment thread
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
}
2 changes: 1 addition & 1 deletion tests/Moq.Analyzers.Benchmarks/SquiggleCop.Baseline.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@
- {Id: HAA0603, Title: Delegate allocation from a method group, Category: Performance, DefaultSeverity: Warning, IsEnabledByDefault: true, EffectiveSeverities: [Error], IsEverSuppressed: false}
- {Id: HAA0604, Title: Delegate allocation from a method group, Category: Performance, DefaultSeverity: Note, IsEnabledByDefault: true, EffectiveSeverities: [Note], IsEverSuppressed: false}
- {Id: IDE0004, Title: Remove Unnecessary Cast, Category: Style, DefaultSeverity: Note, IsEnabledByDefault: true, EffectiveSeverities: [Note], IsEverSuppressed: true}
- {Id: IDE0005, Title: Using directive is unnecessary., Category: Style, DefaultSeverity: Note, IsEnabledByDefault: true, EffectiveSeverities: [Note], IsEverSuppressed: false}
- {Id: IDE0005, Title: Using directive is unnecessary., Category: Style, DefaultSeverity: Note, IsEnabledByDefault: true, EffectiveSeverities: [Note], IsEverSuppressed: true}
- {Id: IDE0005_gen, Title: Using directive is unnecessary., Category: Style, DefaultSeverity: Note, IsEnabledByDefault: true, EffectiveSeverities: [Note], IsEverSuppressed: true}
- {Id: IDE0007, Title: Use implicit type, Category: Style, DefaultSeverity: Note, IsEnabledByDefault: true, EffectiveSeverities: [Note], IsEverSuppressed: false}
- {Id: IDE0008, Title: Use explicit type, Category: Style, DefaultSeverity: Note, IsEnabledByDefault: true, EffectiveSeverities: [Note], IsEverSuppressed: false}
Expand Down
53 changes: 52 additions & 1 deletion tests/Moq.Analyzers.Test/Common/EnumerableExtensionsTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace Moq.Analyzers.Test.Common;
using System.Collections;

Check failure on line 1 in tests/Moq.Analyzers.Test/Common/EnumerableExtensionsTests.cs

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

tests/Moq.Analyzers.Test/Common/EnumerableExtensionsTests.cs#L1

Provide an 'AssemblyVersion' attribute for assembly 'srcassembly.dll'.

namespace Moq.Analyzers.Test.Common;

public class EnumerableExtensionsTests
{
Expand Down Expand Up @@ -73,4 +75,53 @@
int result = source.DefaultIfNotSingle(x => x > 5);
Assert.Equal(0, result);
}

[Fact]
public void DefaultIfNotSingle_StopsEnumeratingAfterSecondMatch()
{
CountingEnumerable<int> source = new(new[] { 1, 2, 3, 4 });
int result = source.DefaultIfNotSingle(x => x > 1);

Assert.Equal(0, result);
Comment thread
rjmurillo marked this conversation as resolved.
Assert.Equal(3, source.Count);
}

[Fact]
public void DefaultIfNotSingle_ThrowsArgumentNullException_WhenSourceIsNull()
{
IEnumerable<int> source = null!;

Check failure on line 92 in tests/Moq.Analyzers.Test/Common/EnumerableExtensionsTests.cs

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

tests/Moq.Analyzers.Test/Common/EnumerableExtensionsTests.cs#L92

Add the 'const' modifier to 'source'.
Assert.Throws<ArgumentNullException>(() => source.DefaultIfNotSingle(x => true));

Check failure on line 93 in tests/Moq.Analyzers.Test/Common/EnumerableExtensionsTests.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-24.04-arm)

Check failure on line 93 in tests/Moq.Analyzers.Test/Common/EnumerableExtensionsTests.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-24.04-arm)

Check failure on line 93 in tests/Moq.Analyzers.Test/Common/EnumerableExtensionsTests.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest)

Check failure on line 93 in tests/Moq.Analyzers.Test/Common/EnumerableExtensionsTests.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest)

}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

[Fact]
public void DefaultIfNotSingle_ThrowsArgumentNullException_WhenPredicateIsNull()
{
IEnumerable<int> source = new List<int> { 1, 2, 3 };
Assert.Throws<ArgumentNullException>(() => source.DefaultIfNotSingle(null!));

Check failure on line 100 in tests/Moq.Analyzers.Test/Common/EnumerableExtensionsTests.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-24.04-arm)

Check failure on line 100 in tests/Moq.Analyzers.Test/Common/EnumerableExtensionsTests.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-24.04-arm)

Check failure on line 100 in tests/Moq.Analyzers.Test/Common/EnumerableExtensionsTests.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest)

Check failure on line 100 in tests/Moq.Analyzers.Test/Common/EnumerableExtensionsTests.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest)

}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

[Fact]
public void DefaultIfNotSingle_ImmutableArray_ThrowsArgumentNullException_WhenPredicateIsNull()
{
var source = ImmutableArray.Create(1, 2, 3);
Assert.Throws<ArgumentNullException>(() => source.DefaultIfNotSingle(null!));

Check failure on line 107 in tests/Moq.Analyzers.Test/Common/EnumerableExtensionsTests.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-24.04-arm)

Check failure on line 107 in tests/Moq.Analyzers.Test/Common/EnumerableExtensionsTests.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-24.04-arm)

Check failure on line 107 in tests/Moq.Analyzers.Test/Common/EnumerableExtensionsTests.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest)

Check failure on line 107 in tests/Moq.Analyzers.Test/Common/EnumerableExtensionsTests.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest)

}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

private sealed class CountingEnumerable<T>(IEnumerable<T> items) : IEnumerable<T>
{
private readonly IEnumerable<T> _items = items;

public int Count { get; private set; }

public IEnumerator<T> GetEnumerator()
{
foreach (T item in _items)
{
Count++;
yield return item;
}
}

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
Loading