diff --git a/.gitignore b/.gitignore
index 2fdfcf347..b9bb1fcb4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -289,3 +289,5 @@ StrykerLogs/
*.DotSettings
/src/Stryker.CLI/TestStatisticsAnalyzer/test-stats-report.json
.DS_Store
+
+*.received.*
\ No newline at end of file
diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props
index dcb572bf3..7f60d4d58 100644
--- a/src/Directory.Packages.props
+++ b/src/Directory.Packages.props
@@ -13,6 +13,7 @@
+
diff --git a/src/Stryker.Abstractions/IReadOnlyMutant.cs b/src/Stryker.Abstractions/IReadOnlyMutant.cs
index 9a46090c2..1ef0a8e8c 100644
--- a/src/Stryker.Abstractions/IReadOnlyMutant.cs
+++ b/src/Stryker.Abstractions/IReadOnlyMutant.cs
@@ -1,5 +1,6 @@
using Stryker.Abstractions.TestRunners;
using Stryker.Abstractions.Mutants;
+using Microsoft.CodeAnalysis;
namespace Stryker.Abstractions;
@@ -17,4 +18,6 @@ public interface IReadOnlyMutant
ITestGuids AssessingTests { get; }
bool CountForStats { get; }
bool IsStaticValue { get; }
+ string ReplacementText => Mutation.ReplacementNode.ToString();
+ FileLinePositionSpan OriginalLocation => Mutation.OriginalNode.GetLocation().GetMappedLineSpan();
}
diff --git a/src/Stryker.Abstractions/Mutation.cs b/src/Stryker.Abstractions/Mutation.cs
index a34be4672..99dc9705b 100644
--- a/src/Stryker.Abstractions/Mutation.cs
+++ b/src/Stryker.Abstractions/Mutation.cs
@@ -1,3 +1,4 @@
+using System;
using Microsoft.CodeAnalysis;
using Stryker.Abstractions.Mutators;
@@ -13,4 +14,8 @@ public class Mutation
public string DisplayName { get; set; }
public Mutator Type { get; set; }
public string Description { get; set; }
+
+ public string? ReplacementText { get; set; }
+
+ public FileLinePositionSpan? OriginalLocation { get; set; }
}
diff --git a/src/Stryker.Core/Stryker.Core.UnitTest/Mutants/GeneratedRegexOrchestratorTests.cs b/src/Stryker.Core/Stryker.Core.UnitTest/Mutants/GeneratedRegexOrchestratorTests.cs
new file mode 100644
index 000000000..1240296f2
--- /dev/null
+++ b/src/Stryker.Core/Stryker.Core.UnitTest/Mutants/GeneratedRegexOrchestratorTests.cs
@@ -0,0 +1,389 @@
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis;
+using System.IO;
+using System.Text.RegularExpressions;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.Text;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using VerifyMSTest;
+using VerifyTests;
+using System.Runtime.CompilerServices;
+using Serilog.Events;
+using Stryker.Abstractions;
+using Stryker.Abstractions.Mutators;
+using Stryker.Core.InjectedHelpers;
+using Stryker.Core.Mutants;
+using Stryker.Abstractions.Options;
+
+namespace Stryker.Core.UnitTest.Mutants;
+
+[TestClass]
+[UsesVerify]
+public partial class GeneratedRegexOrchestratorTests : TestBase
+{
+ private readonly CsharpMutantOrchestrator _target;
+ private readonly CodeInjection _injector = new();
+ private readonly CSharpParseOptions _previewOptions = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Preview);
+
+ public GeneratedRegexOrchestratorTests() => _target = new CsharpMutantOrchestrator(new MutantPlacer(_injector),
+ options: new StrykerOptions
+ {
+ MutationLevel = MutationLevel.Complete,
+ OptimizationMode = OptimizationModes.CoverageBasedTest,
+ LogOptions = new LogOptions
+ {
+ LogLevel = LogEventLevel.Verbose
+ }
+ });
+
+ private async Task ShouldMutateSourceToExpectedAsync(string methodName, string actual)
+ {
+ var actualNode = _target.Mutate(CSharpSyntaxTree.ParseText(actual, _previewOptions), null);
+ actual = (await actualNode.GetRootAsync()).ToFullString();
+ actual = actual.Replace(_injector.HelperNamespace, "StrykerNamespace");
+ actualNode = CSharpSyntaxTree.ParseText(actual, _previewOptions);
+ actualNode.ShouldNotContainErrors();
+ await Verifier.Verify(actual, "cs").UseMethodName(methodName).IgnoreParameters();
+ }
+
+ private async Task ShouldNotMutateSourceAsync(string actual)
+ {
+ var input = CSharpSyntaxTree.ParseText(actual, _previewOptions);
+ var actualNode = _target.Mutate(input, null);
+ actual = (await actualNode.GetRootAsync()).ToFullString();
+ actual = actual.Replace(_injector.HelperNamespace, "StrykerNamespace");
+ actualNode = CSharpSyntaxTree.ParseText(actual);
+ actualNode.ShouldBeSemantically(input);
+ actualNode.ShouldNotContainErrors();
+ }
+
+ private async Task ShouldMutateCompiledSourceToExpectedAsync(string methodName, string actual)
+ {
+ var cSharpParseOptions = _previewOptions.WithPreprocessorSymbols("GENERATED_REGEX");
+ var syntaxTree = CSharpSyntaxTree.ParseText(actual, cSharpParseOptions);
+ var basePath = Path.GetDirectoryName(typeof(object).Assembly.Location)!;
+
+ var mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location);
+ var regex = MetadataReference.CreateFromFile(typeof(Regex).Assembly.Location);
+ var attribute = MetadataReference.CreateFromFile(Path.Combine(basePath, "System.Runtime.dll"));
+
+ Compilation compilation = CSharpCompilation.Create("MyCompilation",
+ [syntaxTree], [mscorlib, attribute, regex],
+ new CSharpCompilationOptions(OutputKind
+ .DynamicallyLinkedLibrary));
+
+ var regexGeneratorDll = Path.Combine(basePath, "..", "..", "..", "packs", "Microsoft.NETCore.App.Ref",
+ Path.GetFileName(basePath),
+ "analyzers", "dotnet", "cs", "System.Text.RegularExpressions.Generator.dll");
+
+ var sourceGenerator =
+ Activator.CreateInstanceFrom(regexGeneratorDll, "System.Text.RegularExpressions.Generator.RegexGenerator")
+ ?.Unwrap() switch
+ {
+ IIncrementalGenerator ig => ig.AsSourceGenerator(),
+ ISourceGenerator sg => sg,
+ _ => null
+ };
+
+ if (sourceGenerator is not null)
+ {
+ GeneratorDriver driver = CSharpGeneratorDriver.Create([sourceGenerator], [], cSharpParseOptions);
+ driver.RunGeneratorsAndUpdateCompilation(compilation, out compilation, out _);
+
+ if (compilation.GetDiagnostics().Where(static a => a.Severity == DiagnosticSeverity.Error).Any())
+ {
+ Assert.Inconclusive("Initial compilation unsuccessful");
+ }
+ }
+
+ var actualNode = _target.Mutate(syntaxTree, compilation.GetSemanticModel(syntaxTree));
+ actual = (await actualNode.GetRootAsync()).ToFullString();
+ actual = actual.Replace(_injector.HelperNamespace, "StrykerNamespace");
+ actualNode = CSharpSyntaxTree.ParseText(actual, cSharpParseOptions);
+ actualNode.ShouldNotContainErrors();
+ await Verifier.Verify(actual, "cs").UseMethodName(methodName).IgnoreParameters();
+ }
+
+ public static IEnumerable