diff --git a/.github/instructions/csharp.instructions.md b/.github/instructions/csharp.instructions.md
index 0c9b6fbf7..c88669513 100644
--- a/.github/instructions/csharp.instructions.md
+++ b/.github/instructions/csharp.instructions.md
@@ -8,6 +8,20 @@ applyTo: '**/*.cs'
- **For complex changes, see the Decision Trees section below**
+**MANDATORY: Only Valid C# Code in All Tests**
+
+> **You MUST NEVER write or include code in analyzer or code fix tests that produces C# compiler errors.**
+> - All test code must be valid, compilable C#.
+> - Do not write tests for static, const, readonly, or event members if the code would not compile.
+> - Do not include code that triggers CSxxxx errors (e.g., invalid member access, missing members, or illegal syntax).
+> - If a scenario cannot be expressed as valid C#, it is not a valid test case for analyzers or code fixes.
+> - Any test that fails to compile is an immediate failure and must be removed or rewritten.
+
+**Rationale:**
+- Roslyn analyzers and code fixes only operate on valid, successfully parsed C# code. Compiler errors prevent analyzers from running and invalidate the test scenario.
+- Writing invalid code in tests causes build failures, test failures, and wastes review/CI resources.
+- This is a non-negotiable rule. If you are unsure whether a test is valid C#, STOP and request expert guidance.
+
## Primary Instructions
Always read and apply the instructions in [.github/copilot-instructions.md](../copilot-instructions.md) when working on C# source or project files.
@@ -68,9 +82,10 @@ If you update guidance in copilot-instructions.md that affects C# development, e
- Update AnalyzerReleases.Unshipped.md
5. **Update project files if needed**
6. **Run all validations (build, test, lint, Codacy, etc.)**
-7. **Prepare PR with validation evidence for each file type**
-8. **If any diagnostic span or test fails more than once, STOP and escalate**
-9. **If uncertain about Roslyn APIs, Moq semantics, or workflow, escalate**
+7. **Validate code coverage of your changed code**
+8. **Prepare PR with validation evidence for each file type**
+9. **If any diagnostic span or test fails more than once, STOP and escalate**
+10. **If uncertain about Roslyn APIs, Moq semantics, or workflow, escalate**
## Test Data & Sample Inputs/Outputs
@@ -87,3 +102,4 @@ If you update guidance in copilot-instructions.md that affects C# development, e
- Data-driven tests for all fixable patterns
- Performance test if analyzer is non-trivial
- Document test data rationale in comments or PR description
+- Code coverage is automatically generated when `dotnet test --settings ./build/targets/tests/test.runsettings` is run and placed in `./artifacts/TestResults/coverage/Cobertura.xml`
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 21d33a622..7989e78d7 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -44,6 +44,7 @@ jobs:
env:
IS_CODACY_COVERAGE_ALLOWED: ${{ secrets.CODACY_PROJECT_TOKEN != '' }}
IS_QLTY_COVERAGE_ALLOWED: ${{ secrets.QLTY_COVERAGE_TOKEN != '' }}
+ IS_TARGET_MAIN: ${{ github.ref == 'refs/heads/main' }}
RUN_FULL_PERF: ${{ (github.event_name == 'schedule' && github.ref == 'refs/heads/main') || (github.event_name == 'workflow_dispatch' && github.event.inputs.run_performance == 'true') }}
FORCE_PERF_BASELINE: ${{ github.event.inputs.force_baseline }} # This is also used in PerfCore.ps1 to determine if the baseline should be forced
@@ -55,11 +56,25 @@ jobs:
- name: Setup, Restore, and Build Solution
uses: ./.github/actions/setup-restore-build
+ - name: Restore Code Coverage history
+ uses: actions/download-artifact@v4
+ with:
+ name: CoverageHistory-${{ matrix.os }}
+ path: ./artifacts/TestResults/coveragehistory
+ continue-on-error: true
+
- name: Test
run: dotnet test --no-build --configuration Release --settings ./build/targets/tests/test.runsettings
env:
REPORTGENERATOR_LICENSE: ${{ secrets.REPORTGENERATOR_LICENSE }}
+ - name: Upload coverage history
+ uses: actions/upload-artifact@v4
+ if: success() && env.IS_TARGET_MAIN == 'true'
+ with:
+ name: CoverageHistory-${{ matrix.os }}
+ path: ./artifacts/TestResults/coveragehistory
+
- name: Upload binlogs
uses: actions/upload-artifact@v4
if: success() || failure()
@@ -173,6 +188,7 @@ jobs:
$resultsDir = "artifacts/performance/perfResults/baseline/results"
if (Test-Path $resultsDir) {
Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value "### Baseline Performance Results"
+ Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value "Baseline SHA: ${{ steps.get-baseline-sha.outputs.sha }}"
$files = Get-ChildItem -Path $resultsDir -Filter "*-report-github.md" | Sort-Object Name
foreach ($file in $files) {
Get-Content $file.FullName | Out-File -Append -FilePath $env:GITHUB_STEP_SUMMARY
@@ -201,3 +217,11 @@ jobs:
name: performance-${{ matrix.os }}
path: |
./artifacts/performance/**
+
+ - name: Upload artifacts
+ uses: actions/upload-artifact@v4
+ if: success() || failure()
+ with:
+ name: artifacts-${{ matrix.os }}
+ path: ./artifacts
+ if-no-files-found: error
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 5483fa5ba..0540e02f9 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -283,6 +283,10 @@ ci(workflow): add performance testing to nightly builds
Run all unit tests:
`dotnet test --settings ./build/targets/tests/test.runsettings`
All tests must pass. PRs with failing tests will be closed.
+- **Code Coverage:**
+ Code coverage is generated automatically if you use the `test.runsettings` from above, which produces Cobertura format. If you wish to have a different format, you can specify on the command line. Example:
+ `dotnet test --collect:"XPlat Code Coverage" --settings ./build/targets/tests/test.runsettings`
+ PRs must not reduce coverage for critical paths without justification.
- **Codacy Analysis:**
Run Codacy CLI analysis on all changed files. Fix all reported issues before submitting the PR.
- **Evidence Required:**
@@ -290,6 +294,7 @@ ci(workflow): add performance testing to nightly builds
- `dotnet format`
- `dotnet build`
- `dotnet test`
+ - **Code coverage report summary**
- Codacy analysis (if issues were found and fixed)
- **No Received Files:**
Remove any `*.received.*` files before committing.
@@ -665,16 +670,18 @@ Before submitting a PR, ensure:
**What Constitutes Validation Evidence:**
- Test execution logs showing all tests pass
+- **Code coverage report summary and/or screenshots showing coverage for changed code**
- Performance benchmark results (if applicable)
- Screenshots of successful CI runs
- Manual testing results for UI changes
-- Code coverage reports for new features
**Evidence Format:**
- Include logs, screenshots, or links to CI runs
- Provide clear, readable evidence
- Ensure evidence is recent and relevant
-- Link to specific test results or benchmarks
+- Link to specific test results, code coverage reports, or benchmarks
+
+**MANDATORY:** Always validate code coverage after making code or test changes. Code coverage validation is a required step before yielding, submitting, or completing any task. Coverage must be reported as part of the validation evidence in PRs and reviews.
### Evidence of Moq Version Awareness
diff --git a/build/targets/tests/Tests.targets b/build/targets/tests/Tests.targets
index ea4462f20..cb4923a75 100644
--- a/build/targets/tests/Tests.targets
+++ b/build/targets/tests/Tests.targets
@@ -6,6 +6,7 @@
<_TestCoverageGlob>$(ArtifactsTestResultsPath)/**/*.cobertura.xml
<_TestCoverageReportDirectory>$(ArtifactsTestResultsPath)/coverage
+ <_TestCoverageHistoryDirectory>$(ArtifactsTestResultsPath)/coveragehistory
@@ -30,6 +31,7 @@
ProjectDirectory="$(MSBuildProjectDirectory)"
ReportFiles="@(_CoverageFiles)"
TargetDirectory="$(_TestCoverageReportDirectory)"
- ReportTypes="MarkdownSummaryGithub;Cobertura;HtmlInline" />
+ ReportTypes="MarkdownSummaryGithub;Cobertura;HtmlInline"
+ HistoryDirectory="$(_TestCoverageHistoryDirectory)" />
diff --git a/build/targets/tests/test.runsettings b/build/targets/tests/test.runsettings
index 58c393e58..1316d6504 100644
--- a/build/targets/tests/test.runsettings
+++ b/build/targets/tests/test.runsettings
@@ -10,6 +10,8 @@
False
False
+ Obsolete,GeneratedCodeAttribute,CompilerGeneratedAttribute
+ true
^set_.*
^.*Test.*
+ ^Microsoft\.Testing.*
+ ^System\.Diagnostics.*
diff --git a/src/Analyzers/LinqToMocksExpressionShouldBeValidAnalyzer.cs b/src/Analyzers/LinqToMocksExpressionShouldBeValidAnalyzer.cs
index 9828eb770..6659bf082 100644
--- a/src/Analyzers/LinqToMocksExpressionShouldBeValidAnalyzer.cs
+++ b/src/Analyzers/LinqToMocksExpressionShouldBeValidAnalyzer.cs
@@ -1,5 +1,4 @@
using Microsoft.CodeAnalysis.Operations;
-using Moq.Analyzers.Common;
namespace Moq.Analyzers;
diff --git a/src/Analyzers/MockRepositoryVerifyAnalyzer.cs b/src/Analyzers/MockRepositoryVerifyAnalyzer.cs
index ffcfcf44c..ef24c40c5 100644
--- a/src/Analyzers/MockRepositoryVerifyAnalyzer.cs
+++ b/src/Analyzers/MockRepositoryVerifyAnalyzer.cs
@@ -1,5 +1,3 @@
-using System.Diagnostics.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Operations;
namespace Moq.Analyzers;
diff --git a/src/Analyzers/VerifyShouldBeUsedOnlyForOverridableMembersAnalyzer.cs b/src/Analyzers/VerifyShouldBeUsedOnlyForOverridableMembersAnalyzer.cs
index 74b52140d..ba1313652 100644
--- a/src/Analyzers/VerifyShouldBeUsedOnlyForOverridableMembersAnalyzer.cs
+++ b/src/Analyzers/VerifyShouldBeUsedOnlyForOverridableMembersAnalyzer.cs
@@ -1,4 +1,3 @@
-using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis.Operations;
namespace Moq.Analyzers;
diff --git a/src/Common/EventSyntaxExtensions.cs b/src/Common/EventSyntaxExtensions.cs
index 368a028cc..0d16b7966 100644
--- a/src/Common/EventSyntaxExtensions.cs
+++ b/src/Common/EventSyntaxExtensions.cs
@@ -1,6 +1,3 @@
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
-
namespace Moq.Analyzers.Common;
///
diff --git a/tests/Moq.Analyzers.Test/Common/DiagnosticEditPropertiesTests.cs b/tests/Moq.Analyzers.Test/Common/DiagnosticEditPropertiesTests.cs
index 988b4b17a..18ef2aa15 100644
--- a/tests/Moq.Analyzers.Test/Common/DiagnosticEditPropertiesTests.cs
+++ b/tests/Moq.Analyzers.Test/Common/DiagnosticEditPropertiesTests.cs
@@ -11,6 +11,61 @@ public void ToImmutableDictionary_RoundTripsProperties()
Assert.Equal("2", dict[DiagnosticEditProperties.EditPositionKey]);
}
+ [Fact]
+ public void ToImmutableDictionary_AllEnumValues_RoundTrips()
+ {
+ foreach (DiagnosticEditProperties.EditType type in Enum.GetValues(typeof(DiagnosticEditProperties.EditType)))
+ {
+ DiagnosticEditProperties props = new DiagnosticEditProperties { TypeOfEdit = type, EditPosition = 0 };
+ ImmutableDictionary dict = props.ToImmutableDictionary();
+ Assert.Equal(type.ToString(), dict[DiagnosticEditProperties.EditTypeKey]);
+ Assert.Equal("0", dict[DiagnosticEditProperties.EditPositionKey]);
+ Assert.True(DiagnosticEditProperties.TryGetFromImmutableDictionary(dict, out DiagnosticEditProperties? roundTripped));
+ Assert.Equal(type, roundTripped!.TypeOfEdit);
+ }
+ }
+
+ [Fact]
+ public void ToImmutableDictionary_HandlesNegativeAndLargeEditPosition()
+ {
+ DiagnosticEditProperties propsNeg = new DiagnosticEditProperties { TypeOfEdit = DiagnosticEditProperties.EditType.Insert, EditPosition = -1 };
+ ImmutableDictionary dictNeg = propsNeg.ToImmutableDictionary();
+ Assert.Equal("-1", dictNeg[DiagnosticEditProperties.EditPositionKey]);
+ Assert.True(DiagnosticEditProperties.TryGetFromImmutableDictionary(dictNeg, out DiagnosticEditProperties? roundTrippedNeg));
+ Assert.Equal(-1, roundTrippedNeg!.EditPosition);
+
+ DiagnosticEditProperties propsLarge = new DiagnosticEditProperties { TypeOfEdit = DiagnosticEditProperties.EditType.Replace, EditPosition = int.MaxValue };
+ ImmutableDictionary dictLarge = propsLarge.ToImmutableDictionary();
+ Assert.Equal(int.MaxValue.ToString(), dictLarge[DiagnosticEditProperties.EditPositionKey]);
+ Assert.True(DiagnosticEditProperties.TryGetFromImmutableDictionary(dictLarge, out DiagnosticEditProperties? roundTrippedLarge));
+ Assert.Equal(int.MaxValue, roundTrippedLarge!.EditPosition);
+ }
+
+ [Fact]
+ public void TryGetFromImmutableDictionary_IgnoresExtraKeys()
+ {
+ ImmutableDictionary dict = ImmutableDictionary.Empty
+ .Add(DiagnosticEditProperties.EditTypeKey, "Insert")
+ .Add(DiagnosticEditProperties.EditPositionKey, "1")
+ .Add("ExtraKey", "ExtraValue");
+ Assert.True(DiagnosticEditProperties.TryGetFromImmutableDictionary(dict, out DiagnosticEditProperties? props));
+ Assert.Equal(DiagnosticEditProperties.EditType.Insert, props!.TypeOfEdit);
+ Assert.Equal(1, props.EditPosition);
+ }
+
+ [Fact]
+ public void DiagnosticEditProperties_EqualityAndImmutability()
+ {
+ DiagnosticEditProperties a = new DiagnosticEditProperties { TypeOfEdit = DiagnosticEditProperties.EditType.Insert, EditPosition = 1 };
+ DiagnosticEditProperties b = new DiagnosticEditProperties { TypeOfEdit = DiagnosticEditProperties.EditType.Insert, EditPosition = 1 };
+ DiagnosticEditProperties c = new DiagnosticEditProperties { TypeOfEdit = DiagnosticEditProperties.EditType.Replace, EditPosition = 1 };
+ Assert.Equal(a, b);
+ Assert.NotEqual(a, c);
+ Assert.True(a.Equals(b));
+ Assert.False(a.Equals(c));
+ Assert.Equal(a.GetHashCode(), b.GetHashCode());
+ }
+
[Fact]
public void TryGetFromImmutableDictionary_Succeeds_WithValidDictionary()
{
diff --git a/tests/Moq.Analyzers.Test/Common/DiagnosticExtensionsTests.cs b/tests/Moq.Analyzers.Test/Common/DiagnosticExtensionsTests.cs
new file mode 100644
index 000000000..1257c6d39
--- /dev/null
+++ b/tests/Moq.Analyzers.Test/Common/DiagnosticExtensionsTests.cs
@@ -0,0 +1,58 @@
+namespace Moq.Analyzers.Test.Common;
+
+public class DiagnosticExtensionsTests
+{
+ [Fact]
+ public void CreateDiagnostic_FromSyntaxNode_Basic()
+ {
+ SyntaxTree tree = CSharpSyntaxTree.ParseText("class C { void M() {} }");
+ SyntaxNode root = tree.GetRoot();
+ DiagnosticDescriptor rule = new DiagnosticDescriptor("TEST0001", "Test", "Test message", "Test", DiagnosticSeverity.Warning, true);
+ Diagnostic diag = root.CreateDiagnostic(rule);
+ Assert.Equal("TEST0001", diag.Id);
+ Assert.Equal(DiagnosticSeverity.Warning, diag.Severity);
+ }
+
+ [Fact]
+ public void CreateDiagnostic_FromLocation_WithProperties()
+ {
+ SyntaxTree tree = CSharpSyntaxTree.ParseText("class C { void M() {} }");
+ Location loc = tree.GetRoot().GetLocation();
+ DiagnosticDescriptor rule = new DiagnosticDescriptor("TEST0002", "Test2", "Test message 2", "Test", DiagnosticSeverity.Info, true);
+ ImmutableDictionary properties = ImmutableDictionary.Empty.Add("Key", "Value");
+ Diagnostic diag = loc.CreateDiagnostic(rule, properties);
+ Assert.Equal("TEST0002", diag.Id);
+ Assert.Equal("Value", diag.Properties["Key"]);
+ }
+
+ [Fact]
+ public void CreateDiagnostic_FromOperation_DelegatesToSyntax()
+ {
+ SyntaxTree tree = CSharpSyntaxTree.ParseText("class C { void M() {} }");
+ SyntaxNode root = tree.GetRoot();
+ CSharpCompilation compilation = CSharpCompilation.Create("Test", new[] { tree });
+ SemanticModel model = compilation.GetSemanticModel(tree);
+ MethodDeclarationSyntax methodDecl = root.DescendantNodes().OfType().First();
+ Microsoft.CodeAnalysis.IOperation? operation = model.GetOperation(methodDecl);
+ DiagnosticDescriptor rule = new DiagnosticDescriptor("TEST0003", "Test3", "Test message 3", "Test", DiagnosticSeverity.Error, true);
+ Diagnostic diag = operation!.CreateDiagnostic(rule);
+ Assert.Equal("TEST0003", diag.Id);
+ Assert.Equal(DiagnosticSeverity.Error, diag.Severity);
+ }
+
+ [Fact]
+ public void CreateDiagnostic_FromOperation_WithProperties()
+ {
+ SyntaxTree tree = CSharpSyntaxTree.ParseText("class C { void M() {} }");
+ SyntaxNode root = tree.GetRoot();
+ CSharpCompilation compilation = CSharpCompilation.Create("Test", new[] { tree });
+ SemanticModel model = compilation.GetSemanticModel(tree);
+ MethodDeclarationSyntax methodDecl = root.DescendantNodes().OfType().First();
+ Microsoft.CodeAnalysis.IOperation? operation = model.GetOperation(methodDecl);
+ DiagnosticDescriptor rule = new DiagnosticDescriptor("TEST0004", "Test4", "Test message 4", "Test", DiagnosticSeverity.Warning, true);
+ ImmutableDictionary properties = ImmutableDictionary.Empty.Add("Key2", "Value2");
+ Diagnostic diag = operation!.CreateDiagnostic(rule, properties);
+ Assert.Equal("TEST0004", diag.Id);
+ Assert.Equal("Value2", diag.Properties["Key2"]);
+ }
+}
diff --git a/tests/Moq.Analyzers.Test/Common/MoqVerificationHelpersTests.cs b/tests/Moq.Analyzers.Test/Common/MoqVerificationHelpersTests.cs
new file mode 100644
index 000000000..4da0a8c36
--- /dev/null
+++ b/tests/Moq.Analyzers.Test/Common/MoqVerificationHelpersTests.cs
@@ -0,0 +1,86 @@
+using Microsoft.CodeAnalysis.Operations;
+
+namespace Moq.Analyzers.Test.Common;
+
+public class MoqVerificationHelpersTests
+{
+ [Fact]
+ public void ExtractLambdaFromArgument_ReturnsLambda_ForDirectLambda()
+ {
+ string code = @"class C { void M() { System.Action a = () => { }; } }";
+ SyntaxTree tree = CSharpSyntaxTree.ParseText(code);
+ CSharpCompilation compilation = CSharpCompilation.Create("Test", new[] { tree }, new[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location) });
+ SemanticModel model = compilation.GetSemanticModel(tree);
+ ParenthesizedLambdaExpressionSyntax lambda = tree.GetRoot().DescendantNodes().OfType().First();
+ IOperation? operation = model.GetOperation(lambda);
+ IAnonymousFunctionOperation? result = MoqVerificationHelpers.ExtractLambdaFromArgument(operation!);
+ Assert.NotNull(result);
+ Assert.IsAssignableFrom(result);
+ }
+
+ [Fact]
+ public void ExtractLambdaFromArgument_ReturnsLambda_ForSimpleLambda()
+ {
+ string code = @"class C { void M() { System.Func f = x => x.ToString(); } }";
+ SyntaxTree tree = CSharpSyntaxTree.ParseText(code);
+ CSharpCompilation compilation = CSharpCompilation.Create("Test", new[] { tree }, new[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location) });
+ SemanticModel model = compilation.GetSemanticModel(tree);
+ SimpleLambdaExpressionSyntax lambda = tree.GetRoot().DescendantNodes().OfType().First();
+ IOperation? operation = model.GetOperation(lambda);
+ IAnonymousFunctionOperation? result = MoqVerificationHelpers.ExtractLambdaFromArgument(operation!);
+ Assert.NotNull(result);
+ Assert.IsAssignableFrom(result);
+ }
+
+ [Fact]
+ public void ExtractLambdaFromArgument_ReturnsNull_ForMethodGroupConversion()
+ {
+ string code = @"class C { void M() { System.Action a = M2; } void M2() { } }";
+ SyntaxTree tree = CSharpSyntaxTree.ParseText(code);
+ CSharpCompilation compilation = CSharpCompilation.Create("Test", new[] { tree }, new[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location) });
+ SemanticModel model = compilation.GetSemanticModel(tree);
+ IdentifierNameSyntax methodGroup = tree.GetRoot().DescendantNodes().OfType().First(id => string.Equals(id.Identifier.Text, "M2", StringComparison.Ordinal));
+ IOperation? operation = model.GetOperation(methodGroup);
+ IAnonymousFunctionOperation? result = MoqVerificationHelpers.ExtractLambdaFromArgument(operation!);
+ Assert.Null(result);
+ }
+
+ [Fact]
+ public void ExtractLambdaFromArgument_ReturnsNull_ForNullInput()
+ {
+ // Suppress CS8625: Cannot convert null literal to non-nullable reference type
+#pragma warning disable CS8625
+ IAnonymousFunctionOperation? result = MoqVerificationHelpers.ExtractLambdaFromArgument(null);
+#pragma warning restore CS8625
+ Assert.Null(result);
+ }
+
+ [Fact]
+ public void ExtractLambdaFromArgument_ReturnsLambda_ForNestedLambda()
+ {
+ string code = @"class C { void M() { System.Func f = x => () => { }; } }";
+ SyntaxTree tree = CSharpSyntaxTree.ParseText(code);
+ CSharpCompilation compilation = CSharpCompilation.Create("Test", new[] { tree }, new[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location) });
+ SemanticModel model = compilation.GetSemanticModel(tree);
+
+ // Find the inner (nested) lambda
+ ParenthesizedLambdaExpressionSyntax innerLambda = tree.GetRoot().DescendantNodes().OfType().First();
+ IOperation? operation = model.GetOperation(innerLambda);
+ IAnonymousFunctionOperation? result = MoqVerificationHelpers.ExtractLambdaFromArgument(operation!);
+ Assert.NotNull(result);
+ Assert.IsAssignableFrom(result);
+ }
+
+ [Fact]
+ public void ExtractLambdaFromArgument_ReturnsNull_ForNonLambda()
+ {
+ string code = @"class C { void M() { int x = 1; } }";
+ SyntaxTree tree = CSharpSyntaxTree.ParseText(code);
+ CSharpCompilation compilation = CSharpCompilation.Create("Test", new[] { tree }, new[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location) });
+ SemanticModel model = compilation.GetSemanticModel(tree);
+ LiteralExpressionSyntax literal = tree.GetRoot().DescendantNodes().OfType().First();
+ IOperation? operation = model.GetOperation(literal);
+ IAnonymousFunctionOperation? result = MoqVerificationHelpers.ExtractLambdaFromArgument(operation!);
+ Assert.Null(result);
+ }
+}
diff --git a/tests/Moq.Analyzers.Test/EventSetupHandlerShouldMatchEventTypeAnalyzerTests.cs b/tests/Moq.Analyzers.Test/EventSetupHandlerShouldMatchEventTypeAnalyzerTests.cs
index a810f02af..d671232b4 100644
--- a/tests/Moq.Analyzers.Test/EventSetupHandlerShouldMatchEventTypeAnalyzerTests.cs
+++ b/tests/Moq.Analyzers.Test/EventSetupHandlerShouldMatchEventTypeAnalyzerTests.cs
@@ -4,27 +4,72 @@ namespace Moq.Analyzers.Test;
public class EventSetupHandlerShouldMatchEventTypeAnalyzerTests
{
+ // Only one version of each static data source method
+ public static IEnumerable