Skip to content

perf: add Moq1208 benchmarks for issue #777 completion#1089

Open
rjmurillo wants to merge 9 commits intomainfrom
feat/777-moq1208-completion
Open

perf: add Moq1208 benchmarks for issue #777 completion#1089
rjmurillo wants to merge 9 commits intomainfrom
feat/777-moq1208-completion

Conversation

@rjmurillo
Copy link
Copy Markdown
Owner

@rjmurillo rjmurillo commented Mar 15, 2026

Summary

Completes Moq1208 (ReturnsDelegateShouldReturnTask) by adding benchmarks and removing dead code from the fixer.

Closes #777

Changes

  • Added Moq1208ReturnsDelegateBenchmarks.cs following the established benchmark pattern
  • Removed unreachable GenericNameSyntax branch from ReturnsDelegateShouldReturnTaskFixer.cs (Moq has no Returns<T>() generic overload)
  • Added [BenchmarkCategory("Moq1208")] and [Benchmark(Baseline = true)] attributes

Performance

The Moq1208 benchmark is new (no baseline comparison available in CI). First baseline will be established on merge. For reference, comparable IOperation-based analyzers on the same CI runner (ARM64 Neoverse-N2):

Analyzer Diagnostic (1 file) Baseline (1 file) Ratio Allocated
Moq1200 (setup overridable) 2,218 us 235 us 9.5x 215 KB
Moq1100 (callback signature) 6,282 us 236 us 26.9x 615 KB
Moq1000 (sealed class) 5,564 us 489 us 11.8x 151 KB

Moq1208 performs similar work to Moq1200 (inspects invocation return types against the mocked method's return type). Expected overhead should be in the 2,000-6,000 us range per diagnostic file, with ~200 KB allocation. The baseline (empty analyzer) cost is ~235 us regardless of analyzer, dominated by Roslyn compilation setup.

Key performance characteristics:

  • MoqKnownSymbols created per-compilation (not per-operation)
  • Early exit via IsMockReferenced() when Moq is not referenced
  • EnableConcurrentExecution() enabled
  • Zero allocation in the hot path (symbol comparisons only)

Dead code removal justification

The GenericNameSyntax branch in ReplaceReturnsWithReturnsAsync was unreachable:

  1. Moq has no Returns<T>() generic overload
  2. The analyzer's guard at line 45 (ValueText == "Returns") ensures only IdentifierName nodes reach the method
  3. Zero fixer tests exercised the branch

Three defensive null guards in RegisterCodeFixesAsync are retained per project convention (Roslyn CodeFix contract pattern).

Test plan

  • Build passes with PedanticMode (0 warnings)
  • All 3,177 tests pass
  • Benchmark follows established pattern (IterationSetup, Baseline attribute)
  • No regressions detected by PerfDiff CI

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • Tests
    • Added comprehensive test coverage for generic Returns<T> patterns in mock setups with synchronous and asynchronous lambda scenarios.
    • Introduced performance benchmark suite to measure analyzer efficiency across varying configurations and dependency versions.

Add performance benchmarks for the Moq1208 analyzer that detects
Returns() delegate type mismatches on async method setups. Follows
the established benchmark pattern from Moq1206.

Refs: #777

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request completes the remaining deliverables for issue #777 by introducing performance benchmarks for the Moq1208 analyzer. This ensures that the analyzer's performance is measured and monitored, contributing to the overall quality and efficiency of the Moq.Analyzers project.

Highlights

  • Performance Benchmarks: Added performance benchmarks for the Moq1208 analyzer (ReturnsDelegateShouldReturnTaskAnalyzer) to assess its performance characteristics.
Changelog
  • tests/Moq.Analyzers.Benchmarks/Moq1208ReturnsDelegateBenchmarks.cs
    • Added a new benchmark file for Moq1208.
Activity
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 15, 2026

📝 Walkthrough

Walkthrough

Added a clarifying comment to the ReturnsDelegateShouldReturnTaskFixer and introduced a new BenchmarkDotNet test class Moq1208ReturnsDelegateBenchmarks that builds compilations and validates analyzer diagnostics across parameterized inputs.

Changes

Cohort / File(s) Summary
Fixer comment
src/CodeFixes/ReturnsDelegateShouldReturnTaskFixer.cs
Added a clarifying comment inside ReplaceReturnsWithReturnsAsync explaining preservation of generic type arguments (e.g., Returns<T>). No code behavior changes.
Benchmarks
tests/Moq.Analyzers.Benchmarks/Moq1208ReturnsDelegateBenchmarks.cs
Added a new BenchmarkDotNet-instrumented class with FileCount and MoqKey params, SetupCompilation() to create baseline/test compilations, and two benchmark methods validating diagnostics for baseline and test compilations.
Tests (data additions)
tests/Moq.Analyzers.Test/ReturnsDelegateShouldReturnTaskAnalyzerTests.cs
Added two valid test cases covering generic Returns<T> with sync and async lambdas to the existing data set.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • MattKotsenas
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description check ✅ Passed The PR description is comprehensive, covering motivation (completing Moq1208), changes made (benchmarks added, dead code removed), performance analysis, and test plan results.
Linked Issues check ✅ Passed The PR fulfills key objectives from #777: adds performance benchmarks following established pattern, includes diagnostic and baseline measurements, and removes unreachable dead code with justification.
Out of Scope Changes check ✅ Passed All changes directly align with PR objectives: new benchmark class, dead code removal from fixer, and test additions for generic Returns cases are all within scope of completing Moq1208.
Title check ✅ Passed The title 'perf: add Moq1208 benchmarks for issue #777 completion' directly and clearly describes the main change—adding performance benchmarks for the Moq1208 analyzer—which aligns with the changeset's primary purpose of adding benchmark infrastructure and supporting test cases.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/777-moq1208-completion

Comment @coderabbitai help to get the list of available commands and usage tips.

@deepsource-io
Copy link
Copy Markdown

deepsource-io bot commented Mar 15, 2026

DeepSource Code Review

We reviewed changes in 2a88555...dd0251f on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.

See full review on DeepSource ↗

PR Report Card

Overall Grade   Security  

Reliability  

Complexity  

Hygiene  

Code Review Summary

Analyzer Status Updated (UTC) Details
C# Mar 31, 2026 4:58p.m. Review ↗

@coderabbitai coderabbitai bot requested a review from MattKotsenas March 15, 2026 20:49
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds benchmarks for the Moq1208 analyzer, which is a great addition. The implementation is solid. The suggested improvements to the benchmark setup have been retained as they enhance clarity and accuracy of performance measurements, aligning with good practices for performance-sensitive code.

…markCategory

Moq's Returns() has no generic overload so the GenericNameSyntax path in
ReturnsDelegateShouldReturnTaskFixer.ReplaceReturnsWithReturnsAsync was
unreachable. Removing it eliminates the 50% branch coverage gap, simplifies
the fixer, and removes a maintenance liability.

Also adds [BenchmarkCategory("Moq1208")] to the new benchmark class to
match the Moq1201 convention.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@rjmurillo rjmurillo added the analyzers Change that impacts an analyzer behavior label Mar 15, 2026
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Incorrect comment; Returns() does have generic overloads
    • Restored GenericNameSyntax handling to preserve type arguments when replacing Returns with ReturnsAsync, since Moq's IReturns<TMock, TResult> interface does define method-level generic overloads like Returns(Func<T1, TResult>).

…with ReturnsAsync

Moq's IReturns<TMock, TResult> interface defines method-level generic overloads like Returns<T1>(Func<T1, TResult>). When a user writes .Returns<string>(s => 42), Roslyn represents this as GenericNameSyntax. The previous code fix incorrectly dropped these type arguments.

This restores the GenericNameSyntax handling to preserve the developer's explicit type arguments in the generated ReturnsAsync call.
coderabbitai[bot]
coderabbitai bot previously approved these changes Mar 15, 2026
Rename Moq1208WithoutDiagnostics to Moq1208Baseline and add
Baseline = true to align with the convention used in other
benchmark files (e.g., Moq1201AsyncResultBenchmarks).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/Moq.Analyzers.Benchmarks/Moq1208ReturnsDelegateBenchmarks.cs`:
- Around line 12-20: Add XML documentation comments for the public benchmark API
surface: add a summary for the class Moq1208ReturnsDelegateBenchmarks and for
each public member FileCount and MoqKey, and add appropriate <summary>,
<returns> and <param> (if any) for the methods SetupCompilation,
Moq1208WithDiagnostics, and Moq1208Baseline so every public symbol listed has
complete XML docs; ensure summaries describe purpose, parameters and return
values where applicable and follow the project's XML doc style.
- Around line 69-70: The test currently uses null-forgiving on TestCompilation
(e.g., (await TestCompilation!.GetAnalysisResultAsync(CancellationToken.None)))
which can throw opaque NREs if setup didn't run; update the benchmark methods
that call TestCompilation!.GetAnalysisResultAsync (and any similar calls at
lines referenced) to first assert or guard that TestCompilation is not null (for
example throw a clear InvalidOperationException or use Assert.NotNull on
TestCompilation) and only then call GetAnalysisResultAsync; ensure all
occurrences (including the calls around line 86/87) have the same explicit guard
so failures surface as clear setup errors rather than NREs.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 7bc2a7f1-383f-4b79-8029-e0925520e896

📥 Commits

Reviewing files that changed from the base of the PR and between 2356a3a and 2399ec4.

📒 Files selected for processing (2)
  • src/CodeFixes/ReturnsDelegateShouldReturnTaskFixer.cs
  • tests/Moq.Analyzers.Benchmarks/Moq1208ReturnsDelegateBenchmarks.cs

Comment on lines +12 to +20
public class Moq1208ReturnsDelegateBenchmarks
{
#pragma warning disable ECS0900
[Params(1, 1_000)]
#pragma warning restore ECS0900
public int FileCount { get; set; }

[Params("Net80WithOldMoq", "Net80WithNewMoq")]
public string MoqKey { get; set; } = "Net80WithOldMoq";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Add XML documentation for the public benchmark API surface.

This file introduces public members without XML docs (Moq1208ReturnsDelegateBenchmarks, FileCount, MoqKey, SetupCompilation, Moq1208WithDiagnostics, Moq1208Baseline).

As per coding guidelines, **/*.cs: "Ensure all public APIs have complete XML documentation".

Also applies to: 28-28, 66-66, 83-83

🧰 Tools
🪛 GitHub Check: Codacy Static Code Analysis

[notice] 15-15: tests/Moq.Analyzers.Benchmarks/Moq1208ReturnsDelegateBenchmarks.cs#L15
Assign this magic number '1000' to a well-named variable or constant, and use that instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/Moq.Analyzers.Benchmarks/Moq1208ReturnsDelegateBenchmarks.cs` around
lines 12 - 20, Add XML documentation comments for the public benchmark API
surface: add a summary for the class Moq1208ReturnsDelegateBenchmarks and for
each public member FileCount and MoqKey, and add appropriate <summary>,
<returns> and <param> (if any) for the methods SetupCompilation,
Moq1208WithDiagnostics, and Moq1208Baseline so every public symbol listed has
complete XML docs; ensure summaries describe purpose, parameters and return
values where applicable and follow the project's XML doc style.

Comment on lines +69 to +70
(await TestCompilation!
.GetAnalysisResultAsync(CancellationToken.None)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Replace null-forgiving dereferences with explicit setup guards.

Line 69 and Line 86 assume setup always ran; if not, benchmark failures degrade to opaque NREs. Add explicit state checks before analysis calls.

💡 Suggested change
 [Benchmark]
 public async Task Moq1208WithDiagnostics()
 {
+    if (TestCompilation is null)
+    {
+        throw new InvalidOperationException($"{nameof(SetupCompilation)} must run before {nameof(Moq1208WithDiagnostics)}.");
+    }
+
     ImmutableArray<Diagnostic> diagnostics =
-        (await TestCompilation!
+        (await TestCompilation
         .GetAnalysisResultAsync(CancellationToken.None)
         .ConfigureAwait(false))
         .AssertValidAnalysisResult()
         .GetAllDiagnostics();
@@
 [Benchmark(Baseline = true)]
 public async Task Moq1208Baseline()
 {
+    if (BaselineCompilation is null)
+    {
+        throw new InvalidOperationException($"{nameof(SetupCompilation)} must run before {nameof(Moq1208Baseline)}.");
+    }
+
     ImmutableArray<Diagnostic> diagnostics =
-        (await BaselineCompilation!
+        (await BaselineCompilation
         .GetAnalysisResultAsync(CancellationToken.None)
         .ConfigureAwait(false))
         .AssertValidAnalysisResult()
         .GetAllDiagnostics();

Also applies to: 86-87

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/Moq.Analyzers.Benchmarks/Moq1208ReturnsDelegateBenchmarks.cs` around
lines 69 - 70, The test currently uses null-forgiving on TestCompilation (e.g.,
(await TestCompilation!.GetAnalysisResultAsync(CancellationToken.None))) which
can throw opaque NREs if setup didn't run; update the benchmark methods that
call TestCompilation!.GetAnalysisResultAsync (and any similar calls at lines
referenced) to first assert or guard that TestCompilation is not null (for
example throw a clear InvalidOperationException or use Assert.NotNull on
TestCompilation) and only then call GetAnalysisResultAsync; ensure all
occurrences (including the calls around line 86/87) have the same explicit guard
so failures surface as clear setup errors rather than NREs.

Document that Returns<T>(lambda) with explicit generic type arguments
is not detected by the analyzer due to Roslyn target-type inference
masking the delegate return type mismatch. Added to ValidWithCompilerSuppression
to prevent regressions if the analyzer behavior changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
["""new Mock<AsyncService>().Setup(c => c.ProcessAsync(It.IsAny<string>())).Returns<string>(async s => s.Length);"""],
};

return data.WithNamespaces().WithMoqReferenceAssemblyGroups();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found 22 lines of similar code in 2 locations (mass = 74) [qlty:similar-code]

@rjmurillo rjmurillo marked this pull request as ready for review March 16, 2026 04:30
Copilot AI review requested due to automatic review settings March 16, 2026 04:30
@chatgpt-codex-connector
Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds performance benchmarks for the Moq1208 analyzer and updates related tests/fixer commentary to support the rule’s completion work for issue #777.

Changes:

  • Added a new BenchmarkDotNet benchmark suite for Moq1208 to measure analyzer overhead and allocations across multiple source-file counts and Moq versions.
  • Extended Moq1208 analyzer test data with additional Returns<T1>(...) scenarios under compiler-diagnostic suppression.
  • Updated/expanded inline commentary in the Moq1208 code fix about GenericNameSyntax handling for generic Returns<T1>(...) calls.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
tests/Moq.Analyzers.Test/ReturnsDelegateShouldReturnTaskAnalyzerTests.cs Adds new “valid with compiler suppression” test inputs around generic Returns<T1> patterns.
tests/Moq.Analyzers.Benchmarks/Moq1208ReturnsDelegateBenchmarks.cs Introduces a new Moq1208 benchmark class following the repo’s existing benchmark structure and parameters.
src/CodeFixes/ReturnsDelegateShouldReturnTaskFixer.cs Adds explanatory comments around preserving type arguments when rewriting ReturnsReturnsAsync.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +164 to +165
// Generic Returns<T> with sync lambda: target-type inference masks the mismatch (analyzer gap, see PR #1089)
["""new Mock<AsyncService>().Setup(c => c.ProcessAsync(It.IsAny<string>())).Returns<string>(s => s.Length);"""],
Comment on lines +167 to +168
// Generic Returns<T> with async lambda (no mismatch)
["""new Mock<AsyncService>().Setup(c => c.ProcessAsync(It.IsAny<string>())).Returns<string>(async s => s.Length);"""],
Comment on lines +66 to 69
// Moq's IReturns<TMock, TResult> interface defines method-level generic overloads
// like Returns<T1>(Func<T1, TResult>). When a user writes .Returns<string>(s => 42),
// the syntax is GenericNameSyntax. Preserve type arguments to maintain developer intent.
if (oldName is GenericNameSyntax genericName)
@rjmurillo-bot rjmurillo-bot changed the title Add Moq1208 benchmarks for issue #777 completion perf: add Moq1208 benchmarks for issue #777 completion Mar 30, 2026
@CLAassistant
Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
1 out of 2 committers have signed the CLA.

✅ rjmurillo
❌ rjmurillo-bot
You have signed the CLA already but the status is still pending? Let us recheck it.

@rjmurillo-bot rjmurillo-bot enabled auto-merge (squash) March 30, 2026 08:55
@codacy-production
Copy link
Copy Markdown

codacy-production bot commented Mar 31, 2026

Not up to standards ⛔

🔴 Issues 2 minor . 1 medium

Alerts:
⚠ 1 issue (≤ 0 issues of at least medium severity)

Results:
3 new issues

Category Results
BestPractice 1 medium
CodeStyle 2 minor

View in Codacy

🟢 Metrics 6 complexity . 5 duplication

Metric Results
Complexity 6
Duplication 5

View in Codacy

🟢 Coverage ∅ diff coverage · +0.00% coverage variation

Metric Results
Coverage variation +0.00% coverage variation (-1.00%)
Diff coverage diff coverage (95.00%)

View coverage diff in Codacy

Coverage variation details
Coverable lines Covered lines Coverage
Common ancestor commit (2a88555) 2660 2391 89.89%
Head commit (dd0251f) 2660 (+0) 2391 (+0) 89.89% (+0.00%)

Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch: <coverage of head commit> - <coverage of common ancestor commit>

Diff coverage details
Coverable lines Covered lines Diff coverage
Pull request (#1089) 0 0 ∅ (not applicable)

Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified: <covered lines added or modified>/<coverable lines added or modified> * 100%

TIP This summary will be updated as you push new changes. Give us feedback

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

analyzers Change that impacts an analyzer behavior build feature releasable

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tasks for Issue #767: Detect Returns() with Non-Task Delegate on Async Methods

5 participants