From c6e5ac8cae46af9b980dddba1a57b3ac1d13fe93 Mon Sep 17 00:00:00 2001 From: Cyrille DUPUYDAUBY Date: Thu, 21 Nov 2024 18:18:13 +0100 Subject: [PATCH] fix: protect directives during mutation + refactoring of mutators --- .../NetCore/EmptyTestProject/Main.cs | 2 + .../Stryker.Core.UnitTest/AssertExtensions.cs | 37 +++++- .../Initialisation/BuildAnalyzerTestsBase.cs | 18 +-- .../Mutants/CsharpMutantOrchestratorTests.cs | 64 +++++---- .../Mutants/MutantOrchestratorTestsBase.cs | 41 +++--- .../Mutants/StrykerCommentTests.cs | 6 +- .../NullCoalescingExpressionMutatorTests.cs | 4 +- .../Mutants/CsharpMutantOrchestrator.cs | 2 + .../MemberAccessExpressionOrchestrator.cs | 2 +- .../NodeSpecificOrchestrator.cs | 7 +- .../SyntaxNodeOrchestrator.cs | 4 +- .../Stryker.Core/Mutants/MutantPlacer.cs | 6 +- .../Mutators/AssignmentStatementMutator.cs | 5 +- .../Mutators/BinaryExpressionMutator.cs | 10 +- .../Mutators/BinaryPatternMutator.cs | 7 +- .../Stryker.Core/Mutators/BlockMutator.cs | 4 +- .../Stryker.Core/Mutators/BooleanMutator.cs | 5 +- .../Stryker.Core/Mutators/CheckedMutator.cs | 3 +- .../Mutators/CollectionExpressionMutator.cs | 10 +- .../Mutators/ConditionalExpressionMutator.cs | 9 +- .../Mutators/InitializerMutator.cs | 3 +- .../Mutators/InterpolatedStringMutator.cs | 3 +- .../Mutators/IsPatternExpressionMutator.cs | 5 +- .../Stryker.Core/Mutators/LinqMutator.cs | 3 +- .../Stryker.Core/Mutators/MathMutator.cs | 52 ++++---- .../Mutators/NegateConditionMutator.cs | 7 +- .../NullCoalescingExpressionMutator.cs | 7 +- .../Mutators/ObjectCreationMutator.cs | 5 +- .../Mutators/PostfixUnaryMutator.cs | 3 +- .../Mutators/PrefixUnaryMutator.cs | 5 +- .../Stryker.Core/Mutators/RegexMutator.cs | 3 +- .../Mutators/RelationalPatternMutator.cs | 3 +- .../Stryker.Core/Mutators/StatementMutator.cs | 14 +- .../Mutators/StringEmptyMutator.cs | 13 +- .../Mutators/StringMethodMutator.cs | 125 +----------------- .../Mutators/StringMethodToConstantMutator.cs | 60 +++++++++ .../Stryker.Core/Mutators/StringMutator.cs | 9 +- 37 files changed, 291 insertions(+), 275 deletions(-) create mode 100644 integrationtest/TargetProjects/NetCore/EmptyTestProject/Main.cs create mode 100644 src/Stryker.Core/Stryker.Core/Mutators/StringMethodToConstantMutator.cs diff --git a/integrationtest/TargetProjects/NetCore/EmptyTestProject/Main.cs b/integrationtest/TargetProjects/NetCore/EmptyTestProject/Main.cs new file mode 100644 index 000000000..d16764272 --- /dev/null +++ b/integrationtest/TargetProjects/NetCore/EmptyTestProject/Main.cs @@ -0,0 +1,2 @@ + +Console.WriteLine("this is a global statement"); diff --git a/src/Stryker.Core/Stryker.Core.UnitTest/AssertExtensions.cs b/src/Stryker.Core/Stryker.Core.UnitTest/AssertExtensions.cs index 9e9214a36..f372cbb36 100644 --- a/src/Stryker.Core/Stryker.Core.UnitTest/AssertExtensions.cs +++ b/src/Stryker.Core/Stryker.Core.UnitTest/AssertExtensions.cs @@ -1,7 +1,9 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Shouldly; using System; +using System.Collections.Generic; using System.IO.Abstractions.TestingHelpers; using System.Linq; @@ -32,14 +34,21 @@ public static void ShouldBeSemantically(this SyntaxTree actual, SyntaxTree expec if (!isSame) { - // find the different - var actualLines = actual.ToString().Split(Environment.NewLine); + var diff = ScanDiff(actual.GetRoot(), expected.GetRoot()); + + Console.WriteLine(string.Join(Environment.NewLine, diff)); + + } + + // find the different + var actualLines = actual.ToString().Split(Environment.NewLine); var expectedLines = expected.ToString().Split(Environment.NewLine); for (var i = 0; i < actualLines.Length; i++) { if (expectedLines.Length <= i) { isSame.ShouldBeTrue($"AST's are not equivalent. Line[{i + 1}]{Environment.NewLine}actual:{actualLines[i]}{Environment.NewLine}expect: nothing{Environment.NewLine}Actual(full):{Environment.NewLine}{actual}{Environment.NewLine}, expected:{Environment.NewLine}{expected}"); + continue; } if (actualLines[i] != expectedLines[i]) { @@ -47,6 +56,30 @@ public static void ShouldBeSemantically(this SyntaxTree actual, SyntaxTree expec } } } + + private static List ScanDiff(SyntaxNode actual, SyntaxNode expected) + { + var actualChildren = actual.ChildNodes().ToList(); + var expectedChildren = expected.ChildNodes().ToList(); + var failedStatements = new List(); + for (var i = 0; i < actualChildren.Count; i++) + { + if (expectedChildren.Count <= i) + { + failedStatements.Add($"Extra statements: {actualChildren[i]}"); + continue; + } + if ((actualChildren[i] is not StatementSyntax) || (actualChildren[i] is BlockSyntax or IfStatementSyntax or ForStatementSyntax or WhileStatementSyntax )) + { + failedStatements.AddRange(ScanDiff(actualChildren[i], expectedChildren[i])); + continue; + } + if (!actualChildren[i].IsEquivalentTo(expectedChildren[i])) + { + failedStatements.Add($"Not equivalent. Actual:{Environment.NewLine}{actualChildren[i]}{Environment.NewLine}Expected:{Environment.NewLine}{expectedChildren[i]}"); + } + } + return failedStatements; } public static void ShouldBeWithNewlineReplace(this string actual, string expected) diff --git a/src/Stryker.Core/Stryker.Core.UnitTest/Initialisation/BuildAnalyzerTestsBase.cs b/src/Stryker.Core/Stryker.Core.UnitTest/Initialisation/BuildAnalyzerTestsBase.cs index b22658e0f..1444a9e82 100644 --- a/src/Stryker.Core/Stryker.Core.UnitTest/Initialisation/BuildAnalyzerTestsBase.cs +++ b/src/Stryker.Core/Stryker.Core.UnitTest/Initialisation/BuildAnalyzerTestsBase.cs @@ -18,7 +18,7 @@ public class BuildAnalyzerTestsBase : TestBase protected internal const string DefaultFramework = "net6.0"; protected readonly MockFileSystem FileSystem = new(); protected string ProjectPath; - private readonly Dictionary> _projectCache = new(); + private readonly Dictionary> _projectCache = []; protected readonly Mock BuildalyzerProviderMock = new(MockBehavior.Strict); public BuildAnalyzerTestsBase() @@ -41,7 +41,7 @@ protected Mock SourceProjectAnalyzerMock(string csprojPathName IEnumerable projectReferences = null, string framework = DefaultFramework, Func success = null) { var properties = GetSourceProjectDefaultProperties(); - projectReferences ??= new List(); + projectReferences ??= []; return BuildProjectAnalyzerMock(csprojPathName, sourceFiles, properties, projectReferences, [framework], success); } @@ -58,7 +58,7 @@ protected Mock SourceProjectAnalyzerMock(string csprojPathName IEnumerable projectReferences , IEnumerable frameworks, Func success = null) { var properties = GetSourceProjectDefaultProperties(); - projectReferences??= new List(); + projectReferences??= []; return BuildProjectAnalyzerMock(csprojPathName, sourceFiles, properties, projectReferences, frameworks, success); } @@ -81,7 +81,7 @@ public static Dictionary GetSourceProjectDefaultProperties() /// the test project references the production code project and contains no source file protected Mock TestProjectAnalyzerMock(string testCsprojPathName, string csProj, IEnumerable frameworks = null, bool success = true) { - frameworks??=new []{DefaultFramework}; + frameworks??=[DefaultFramework]; var properties = new Dictionary{ { "IsTestProject", "True" }, { "Language", "C#" } }; var projectReferences = string.IsNullOrEmpty(csProj) ? [] : GetProjectResult(csProj, frameworks.First()).ProjectReferences.Append(csProj).ToList(); return BuildProjectAnalyzerMock(testCsprojPathName, [], properties, projectReferences, frameworks, () => success); @@ -110,7 +110,7 @@ private IAnalyzerResult GetProjectResult(string projectFile, string expectedFram /// a tuple with the framework kind first and the version next protected static (FrameworkKind kind, decimal version) ParseFramework(string framework) { - FrameworkKind kind; + decimal version; if (framework.StartsWith("netcoreapp")) @@ -153,7 +153,7 @@ protected enum FrameworkKind /// if the framework is among the target, the best match if available, null otherwise. protected static string PickCompatibleFramework(string framework, IEnumerable frameworks) { - var parsed = ParseFramework(framework); + var (kind, version) = ParseFramework(framework); string bestCandidate = null; var bestVersion = 1.0m; @@ -164,12 +164,12 @@ protected static string PickCompatibleFramework(string framework, IEnumerable parsed.version || parsedCandidate.version <= bestVersion) + if (parsedCandidate.version > version || parsedCandidate.version <= bestVersion) { continue; } @@ -264,7 +264,7 @@ internal Mock BuildProjectAnalyzerMock(string csprojPathName, return projectAnalyzerMock; } - private IAnalyzerResults BuildAnalyzerResultsMock(IDictionary projectAnalyzerResults) + private static IAnalyzerResults BuildAnalyzerResultsMock(IDictionary projectAnalyzerResults) { var analyzerResults = projectAnalyzerResults.Values.ToList(); var sourceProjectAnalyzerResultsMock = new Mock(MockBehavior.Strict); diff --git a/src/Stryker.Core/Stryker.Core.UnitTest/Mutants/CsharpMutantOrchestratorTests.cs b/src/Stryker.Core/Stryker.Core.UnitTest/Mutants/CsharpMutantOrchestratorTests.cs index 59ea3fb88..34b7270e2 100644 --- a/src/Stryker.Core/Stryker.Core.UnitTest/Mutants/CsharpMutantOrchestratorTests.cs +++ b/src/Stryker.Core/Stryker.Core.UnitTest/Mutants/CsharpMutantOrchestratorTests.cs @@ -1,13 +1,10 @@ using System.Collections.Generic; -using System.Collections.Immutable; -using System.IO; using System.Linq; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.VisualStudio.TestTools.UnitTesting; using Shouldly; using Stryker.Abstractions; -using Stryker.Abstractions.Mutants; using Stryker.Abstractions.Mutators; using Stryker.Abstractions.Options; using Stryker.Configuration; @@ -942,8 +939,6 @@ public void ShouldNotMutateTopLevelStatementsIfDisabledByComment() } - [TestMethod] - [TestMethod] public void ShouldMutateChainedMutations() { @@ -1598,6 +1593,7 @@ static string Value(string text) => ShouldMutateSourceInClassToExpected(source, expected); } + [TestMethod] public void ShouldMutateSubStringMethod() { @@ -1625,10 +1621,10 @@ public void ShouldMutateChainedStringMethods() """ static char Value => (StrykerNamespace.MutantControl.IsActive(0)?'\0': - (StrykerNamespace.MutantControl.IsActive(1)?"".ElementAt(2): (StrykerNamespace.MutantControl.IsActive(2)?"test ".ToUpper().Trim().PadRight(2).Substring(2).ElementAt(2): - (StrykerNamespace.MutantControl.IsActive(3)?"".PadLeft(2).Substring(2).ElementAt(2): (StrykerNamespace.MutantControl.IsActive(4)?"test ".ToLower().Trim().PadLeft(2).Substring(2).ElementAt(2): + (StrykerNamespace.MutantControl.IsActive(1)?"".ElementAt(2): + (StrykerNamespace.MutantControl.IsActive(3)?"".PadLeft(2).Substring(2).ElementAt(2): (StrykerNamespace.MutantControl.IsActive(5)?"":"test ").ToUpper().Trim().PadLeft(2).Substring(2).ElementAt(2)))))); """; @@ -1666,7 +1662,7 @@ public void ShouldMutateCollectionExpressionSpanProperty() var source = "static ReadOnlySpan Value => [1, 2, 3];"; var expected = - "static ReadOnlySpan Value => (StrykerNamespace.MutantControl.IsActive(0)?[]:[1,2,3]);"; + "static ReadOnlySpan Value => (StrykerNamespace.MutantControl.IsActive(0)?(ReadOnlySpan)[]:[1,2,3]);"; ShouldMutateSourceInClassToExpected(source, expected); } @@ -1690,7 +1686,7 @@ public void M() { int[] bcd = [1, .. abc, 3]; } else { int[] abc = { 5, 5 }; - int[] bcd = (StrykerNamespace.MutantControl.IsActive(2)?[]:[1, .. abc, 3]); + int[] bcd = (StrykerNamespace.MutantControl.IsActive(2)?(int[])[]:[1, .. abc, 3]); } } } @@ -1719,7 +1715,7 @@ public void M() { } else { int[] abc = {5, 5}; var bcd = (int[])( - StrykerNamespace.MutantControl.IsActive(2) ? [] : [ 1, ..abc, 3 ]); + StrykerNamespace.MutantControl.IsActive(2) ? (int[])[] : [ 1, ..abc, 3 ]); } } } @@ -1743,7 +1739,7 @@ public void M() { if (StrykerNamespace.MutantControl.IsActive(0)) { } else { // Stryker disable String : Not mutation under test - Span weekDays = (StrykerNamespace.MutantControl.IsActive(1) ? [] : ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]); + Span weekDays = (StrykerNamespace.MutantControl.IsActive(1) ? (Span)[] : ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]); } } """; @@ -1773,14 +1769,12 @@ public void Example() """ // Initialize private field: // Stryker disable String : Not mutation under test - private static readonly ImmutableArray _months = - StrykerNamespace.MutantContext.TrackValue( - () =>(StrykerNamespace.MutantControl.IsActive(0) ? [] : ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"])); + private static readonly ImmutableArray _months =StrykerNamespace.MutantContext.TrackValue(() =>(StrykerNamespace.MutantControl.IsActive(0) ? (ImmutableArray)[] : ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"])); // property with expression body: public IEnumerable MaxDays => (StrykerNamespace.MutantControl.IsActive(13) - ? [] + ? (IEnumerable)[] : [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ]); public int Sum(IEnumerable values) => @@ -1790,8 +1784,7 @@ public void Example() { if (StrykerNamespace.MutantControl.IsActive(15)) { } else { // As a parameter: - int sum = Sum( - (StrykerNamespace.MutantControl.IsActive(16) ? [] : [ 1, 2, 3, 4, 5 ])); + int sum = Sum((StrykerNamespace.MutantControl.IsActive(16)?(IEnumerable)[]:[1, 2, 3, 4, 5])); } } """; @@ -1834,7 +1827,7 @@ public void M() { string oxygen = "O"; string fluorine = "F"; string neon = "Ne"; - string[] elements = (StrykerNamespace.MutantControl.IsActive(11) ? [] : [ + string[] elements = (StrykerNamespace.MutantControl.IsActive(11) ? (string[])[] : [ hydrogen, helium, lithium, beryllium, boron, carbon, nitrogen, oxygen, fluorine, neon ]); @@ -1863,14 +1856,14 @@ public void M() { // Stryker disable String : Not mutation under test if (StrykerNamespace.MutantControl.IsActive(0)) { } else { - string[] vowels = (StrykerNamespace.MutantControl.IsActive(1) ? [] : ["a", "e", "i", "o", "u"]); - string[] consonants = (StrykerNamespace.MutantControl.IsActive(7) ? [] : [ + string[] vowels = (StrykerNamespace.MutantControl.IsActive(1) ? (string[])[] : ["a", "e", "i", "o", "u"]); + string[] consonants = (StrykerNamespace.MutantControl.IsActive(7) ? (string[])[] : [ "b", "c", "d", "f", "g", "h", "j", "k", "l", "m", "n", "p", "q", "r", "s", "t", "v", "w", "x", "z" ]); string[] alphabet = (StrykerNamespace.MutantControl.IsActive(28) - ? [] + ? (string[])[] : [..vowels, ..consonants, "y"]); } } @@ -1896,7 +1889,7 @@ public void M() { if (StrykerNamespace.MutantControl.IsActive(1)) { ; } else { - Iter((StrykerNamespace.MutantControl.IsActive(2) ? [] : [ 1 ])); + Iter((StrykerNamespace.MutantControl.IsActive(2)?(IList)[]:[1])); } } } @@ -1911,7 +1904,7 @@ public void ShouldMutateNestedImplicitCollectionExpression() var source = "static int[][] Value => [[1, 2], [3]];"; var expected = - "static int[][] Value => (StrykerNamespace.MutantControl.IsActive(0)?[]:[(StrykerNamespace.MutantControl.IsActive(1)?[]:[1, 2]), (StrykerNamespace.MutantControl.IsActive(2)?[]:[3])]);"; + "static int[][] Value => (StrykerNamespace.MutantControl.IsActive(0)?(int[][])[]:[(StrykerNamespace.MutantControl.IsActive(1)?(int[])[]:[1, 2]), (StrykerNamespace.MutantControl.IsActive(2)?(int[])[]:[3])]);"; ShouldMutateSourceInClassToExpected(source, expected); } @@ -1921,7 +1914,30 @@ public void ShouldMutateNestedExplicitCollectionExpression() var source = "static int[][] Value => [[1, 2], new int[] { 3 }];"; var expected = - "static int[][] Value => (StrykerNamespace.MutantControl.IsActive(0)?[]:[(StrykerNamespace.MutantControl.IsActive(1)?[]:[1, 2]), (StrykerNamespace.MutantControl.IsActive(2)?new int[] {}:new int[] { 3 })]);"; + "static int[][] Value => (StrykerNamespace.MutantControl.IsActive(0)?(int[][])[]:[(StrykerNamespace.MutantControl.IsActive(1)?(int[])[]:[1, 2]), (StrykerNamespace.MutantControl.IsActive(2)?new int[] {}:new int[] { 3 })]);"; ShouldMutateSourceInClassToExpected(source, expected); } + + [TestMethod] + public void ShouldProtectDirectives() + { + var source = @"public void SomeMethod() { + var x = 0; +#if !DEBUG + x++; +#endif +}"; + var expected = @"public void SomeMethod() {if(StrykerNamespace.MutantControl.IsActive(0)){}else{ + var x = 0; +if(StrykerNamespace.MutantControl.IsActive(1)){;}else{if(StrykerNamespace.MutantControl.IsActive(2)){ #if !DEBUG + x--; +}else{ #if !DEBUG + x++; +}} #endif +}}"; + + ShouldMutateSourceInClassToExpected(source, expected); + } + + } diff --git a/src/Stryker.Core/Stryker.Core.UnitTest/Mutants/MutantOrchestratorTestsBase.cs b/src/Stryker.Core/Stryker.Core.UnitTest/Mutants/MutantOrchestratorTestsBase.cs index ba8aa928f..c556dddf3 100644 --- a/src/Stryker.Core/Stryker.Core.UnitTest/Mutants/MutantOrchestratorTestsBase.cs +++ b/src/Stryker.Core/Stryker.Core.UnitTest/Mutants/MutantOrchestratorTestsBase.cs @@ -4,7 +4,10 @@ using Stryker.Core.Mutants; using Stryker.Core.InjectedHelpers; using Stryker.Abstractions.Options; -using System.Linq.Expressions; +using Microsoft.CodeAnalysis; +using System.Linq; +using System.Collections.Generic; +using System; namespace Stryker.Core.UnitTest.Mutants; @@ -25,11 +28,12 @@ public class MutantOrchestratorTestsBase : TestBase protected void ShouldMutateSourceToExpected(string actual, string expected) { var syntaxTree = CSharpSyntaxTree.ParseText(actual); - - var compilation = CSharpCompilation.Create(null) - .AddSyntaxTrees(syntaxTree); - var model = compilation.GetSemanticModel(syntaxTree); - var actualNode = Target.Mutate(syntaxTree, model); + Type[] typeToLoad = [typeof(object), typeof(List<>), typeof(Enumerable), typeof(Nullable<>)]; + MetadataReference[] references = typeToLoad.Select( t=> MetadataReference.CreateFromFile(t.Assembly.Location)).ToArray(); + var compilation = CSharpCompilation.Create(null).WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary) + .WithNullableContextOptions(NullableContextOptions.Enable)) + .AddSyntaxTrees(syntaxTree).WithReferences(references); + var actualNode = Target.Mutate(syntaxTree, compilation.GetSemanticModel(syntaxTree)); actual = actualNode.GetRoot().ToFullString(); actual = actual.Replace(Injector.HelperNamespace, "StrykerNamespace"); actualNode = CSharpSyntaxTree.ParseText(actual); @@ -40,21 +44,26 @@ protected void ShouldMutateSourceToExpected(string actual, string expected) protected void ShouldMutateSourceInClassToExpected(string actual, string expected) { - actual = @"using System; + var initBlock=@"using System; +using System.Linq; using System.Collections.Generic; using System.Text; -namespace StrykerNet.UnitTest.Mutants.TestResources; +namespace StrykerNet.UnitTest.Mutants.TestResources;"; + + + actual = string.Format(@"{0} class TestClass -{" + actual + @"} -"; +{{ +{1} +}} +", initBlock, actual); - expected = @"using System; -using System.Collections.Generic; -using System.Text; -namespace StrykerNet.UnitTest.Mutants.TestResources; + expected = string.Format(@"{0} class TestClass -{" + expected + @"} -"; +{{ +{1} +}} +", initBlock, expected); ShouldMutateSourceToExpected(actual, expected); } } diff --git a/src/Stryker.Core/Stryker.Core.UnitTest/Mutants/StrykerCommentTests.cs b/src/Stryker.Core/Stryker.Core.UnitTest/Mutants/StrykerCommentTests.cs index d98a0e0d9..428df1f23 100644 --- a/src/Stryker.Core/Stryker.Core.UnitTest/Mutants/StrykerCommentTests.cs +++ b/src/Stryker.Core/Stryker.Core.UnitTest/Mutants/StrykerCommentTests.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Shouldly; using Stryker.Abstractions.Mutants; @@ -10,7 +6,7 @@ namespace Stryker.Core.UnitTest.Mutants; internal class StrykerCommentTests : MutantOrchestratorTestsBase { - [TestMethod] + [TestMethod] public void ShouldNotMutateIfDisabledByComment() { var source = @"public void SomeMethod() { diff --git a/src/Stryker.Core/Stryker.Core.UnitTest/Mutators/NullCoalescingExpressionMutatorTests.cs b/src/Stryker.Core/Stryker.Core.UnitTest/Mutators/NullCoalescingExpressionMutatorTests.cs index 2f6fe5135..3fade2895 100644 --- a/src/Stryker.Core/Stryker.Core.UnitTest/Mutators/NullCoalescingExpressionMutatorTests.cs +++ b/src/Stryker.Core/Stryker.Core.UnitTest/Mutators/NullCoalescingExpressionMutatorTests.cs @@ -1,3 +1,4 @@ +using System; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -25,7 +26,7 @@ public void ShouldMutate() // Arrange var target = new NullCoalescingExpressionMutator(); var originalExpressionString = "a ?? b"; - var expectedExpressionStrings = new[] { "a", "b", "b ?? a" }; + var expectedExpressionStrings = new[] { "a", "b", "b?? a" }; var originalExpression = SyntaxFactory.ParseExpression(originalExpressionString); // Act @@ -61,6 +62,7 @@ public void ShouldNotMutateLeftToRightOrRemoveLeftIfNotNullable() { var syntaxTree = CSharpSyntaxTree.ParseText( """ + using System; public TimeSpan? GetLocalDateTime(DateTimeOffset startTime, DateTimeOffset? endTime) { return (endTime ?? startTime).LocalDateTime; diff --git a/src/Stryker.Core/Stryker.Core/Mutants/CsharpMutantOrchestrator.cs b/src/Stryker.Core/Stryker.Core/Mutants/CsharpMutantOrchestrator.cs index 188a29b61..c1ce48874 100644 --- a/src/Stryker.Core/Stryker.Core/Mutants/CsharpMutantOrchestrator.cs +++ b/src/Stryker.Core/Stryker.Core/Mutants/CsharpMutantOrchestrator.cs @@ -5,6 +5,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.Extensions.Logging; +using Spectre.Console; using Stryker.Abstractions; using Stryker.Abstractions.Logging; using Stryker.Abstractions.Mutants; @@ -144,6 +145,7 @@ internal IEnumerable GenerateMutationsForNode(SyntaxNode current, Semant { foreach (var mutation in mutator.Mutate(current, semanticModel, Options)) { + mutation.OriginalNode = current; var newMutant = CreateNewMutant(mutation, context); // Skip if the mutant is a duplicate if (IsMutantDuplicate(newMutant, mutation)) diff --git a/src/Stryker.Core/Stryker.Core/Mutants/CsharpNodeOrchestrators/MemberAccessExpressionOrchestrator.cs b/src/Stryker.Core/Stryker.Core/Mutants/CsharpNodeOrchestrators/MemberAccessExpressionOrchestrator.cs index 72c05f8af..16342d020 100644 --- a/src/Stryker.Core/Stryker.Core/Mutants/CsharpNodeOrchestrators/MemberAccessExpressionOrchestrator.cs +++ b/src/Stryker.Core/Stryker.Core/Mutants/CsharpNodeOrchestrators/MemberAccessExpressionOrchestrator.cs @@ -1,7 +1,7 @@ using System; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Stryker.Core.Mutants; + namespace Stryker.Core.Mutants.CsharpNodeOrchestrators; diff --git a/src/Stryker.Core/Stryker.Core/Mutants/CsharpNodeOrchestrators/NodeSpecificOrchestrator.cs b/src/Stryker.Core/Stryker.Core/Mutants/CsharpNodeOrchestrators/NodeSpecificOrchestrator.cs index eadc01d20..4edf65ed9 100644 --- a/src/Stryker.Core/Stryker.Core/Mutants/CsharpNodeOrchestrators/NodeSpecificOrchestrator.cs +++ b/src/Stryker.Core/Stryker.Core/Mutants/CsharpNodeOrchestrators/NodeSpecificOrchestrator.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using Microsoft.CodeAnalysis; -using Stryker.Core.Mutants; namespace Stryker.Core.Mutants.CsharpNodeOrchestrators; @@ -57,7 +56,8 @@ internal class NodeSpecificOrchestrator : INodeOrchestrator where /// Mutation context. /// A list of s for the given node. /// You should not override this, unless you want to block mutation generation for the node. Then returns and empty list. - protected virtual IEnumerable GenerateMutationForNode(TNode node, SemanticModel semanticModel, MutationContext context) => context.GenerateMutantsForNode(node, semanticModel); + protected virtual IEnumerable GenerateMutationForNode(TNode node, SemanticModel semanticModel, MutationContext context) => + context.GenerateMutantsForNode(node, semanticModel); /// /// Stores provided mutations. @@ -68,7 +68,8 @@ internal class NodeSpecificOrchestrator : INodeOrchestrator where /// A instance storing existing mutations as well as the one provided /// You need to override this method if the generated mutations cannot be injected in place (via a conditional operator) but must be controlled /// at the statement or block level. Default implementation does nothing. - protected virtual MutationContext StoreMutations(TNode node, IEnumerable mutations, MutationContext context) => context.AddMutations(mutations); + protected virtual MutationContext StoreMutations(TNode node, IEnumerable mutations, MutationContext context) => + context.AddMutations(mutations); /// /// Mutate children, grandchildren (recursively). diff --git a/src/Stryker.Core/Stryker.Core/Mutants/CsharpNodeOrchestrators/SyntaxNodeOrchestrator.cs b/src/Stryker.Core/Stryker.Core/Mutants/CsharpNodeOrchestrators/SyntaxNodeOrchestrator.cs index 473f46bf7..383f06369 100644 --- a/src/Stryker.Core/Stryker.Core/Mutants/CsharpNodeOrchestrators/SyntaxNodeOrchestrator.cs +++ b/src/Stryker.Core/Stryker.Core/Mutants/CsharpNodeOrchestrators/SyntaxNodeOrchestrator.cs @@ -1,7 +1,5 @@ using System.Collections.Generic; -using System.Linq; using Microsoft.CodeAnalysis; -using Stryker.Core.Mutants; namespace Stryker.Core.Mutants.CsharpNodeOrchestrators; @@ -11,5 +9,5 @@ namespace Stryker.Core.Mutants.CsharpNodeOrchestrators; internal class SyntaxNodeOrchestrator : NodeSpecificOrchestrator { // we don't mutate this node - protected override IEnumerable GenerateMutationForNode(SyntaxNode node, SemanticModel semanticModel, MutationContext context) => Enumerable.Empty(); + protected override IEnumerable GenerateMutationForNode(SyntaxNode node, SemanticModel semanticModel, MutationContext context) => []; } diff --git a/src/Stryker.Core/Stryker.Core/Mutants/MutantPlacer.cs b/src/Stryker.Core/Stryker.Core/Mutants/MutantPlacer.cs index a432eee2f..cde1c1993 100644 --- a/src/Stryker.Core/Stryker.Core/Mutants/MutantPlacer.cs +++ b/src/Stryker.Core/Stryker.Core/Mutants/MutantPlacer.cs @@ -19,8 +19,8 @@ public class MutantPlacer private const string MutationTypeMarker = "MutationType"; public static readonly string Injector = "Injector"; - private static readonly Dictionary instrumentEngines = new(); - private static readonly HashSet requireRecursiveRemoval = new(); + private static readonly Dictionary instrumentEngines = []; + private static readonly HashSet requireRecursiveRemoval = []; private static readonly StaticInstrumentationEngine StaticEngine = new(); private static readonly StaticInitializerMarkerEngine StaticInitializerEngine = new(); @@ -33,7 +33,7 @@ public class MutantPlacer private ExpressionSyntax _binaryExpression; private SyntaxNode _placeHolderNode; - public static IEnumerable MutationMarkers => new[] { MutationIdMarker, MutationTypeMarker, Injector }; + public static IEnumerable MutationMarkers => [MutationIdMarker, MutationTypeMarker, Injector]; public MutantPlacer(CodeInjection injection) => _injection = injection; diff --git a/src/Stryker.Core/Stryker.Core/Mutators/AssignmentStatementMutator.cs b/src/Stryker.Core/Stryker.Core/Mutators/AssignmentStatementMutator.cs index 9284663de..88f918f43 100644 --- a/src/Stryker.Core/Stryker.Core/Mutators/AssignmentStatementMutator.cs +++ b/src/Stryker.Core/Stryker.Core/Mutators/AssignmentStatementMutator.cs @@ -45,8 +45,9 @@ public override IEnumerable ApplyMutations(AssignmentExpressionSyntax foreach (var targetAssignmentKind in targetAssignmentKinds) { - var replacementNode = SyntaxFactory.AssignmentExpression(targetAssignmentKind, node.Left, node.Right); - replacementNode = replacementNode.WithOperatorToken(replacementNode.OperatorToken.WithTriviaFrom(node.OperatorToken)); + var replacementNode = + SyntaxFactory.AssignmentExpression(targetAssignmentKind, node.Left.WithCleanTrivia(), node.Right.WithCleanTrivia()); + replacementNode = replacementNode.WithOperatorToken(replacementNode.OperatorToken.WithCleanTriviaFrom(node.OperatorToken)); yield return new Mutation { OriginalNode = node, diff --git a/src/Stryker.Core/Stryker.Core/Mutators/BinaryExpressionMutator.cs b/src/Stryker.Core/Stryker.Core/Mutators/BinaryExpressionMutator.cs index cb5c74c36..3cbd3f411 100644 --- a/src/Stryker.Core/Stryker.Core/Mutators/BinaryExpressionMutator.cs +++ b/src/Stryker.Core/Stryker.Core/Mutators/BinaryExpressionMutator.cs @@ -22,7 +22,7 @@ public MutationData(Mutator mutator, params SyntaxKind[] kindsToMutate) } } - private static readonly Dictionary _kindsToMutate = new Dictionary() + private static readonly Dictionary _kindsToMutate = new() { { SyntaxKind.SubtractExpression, new MutationData(Mutator.Arithmetic, SyntaxKind.AddExpression) }, { SyntaxKind.AddExpression, new MutationData(Mutator.Arithmetic, SyntaxKind.SubtractExpression) }, @@ -57,9 +57,9 @@ public override IEnumerable ApplyMutations(BinaryExpressionSyntax node { foreach (var mutationKind in mutationData.KindsToMutate) { - var replacementNode = SyntaxFactory.BinaryExpression(mutationKind, node.Left, node.Right); + var replacementNode = SyntaxFactory.BinaryExpression(mutationKind, node.Left.WithCleanTrivia(), node.Right.WithCleanTrivia()); // make sure the trivia stays in place for displaying - replacementNode = replacementNode.WithOperatorToken(replacementNode.OperatorToken.WithTriviaFrom(node.OperatorToken)); + replacementNode = replacementNode.WithOperatorToken(replacementNode.OperatorToken.WithCleanTriviaFrom(node.OperatorToken)); yield return new Mutation() { OriginalNode = node, @@ -79,8 +79,8 @@ public override IEnumerable ApplyMutations(BinaryExpressionSyntax node private static Mutation GetLogicalMutation(BinaryExpressionSyntax node) { - var replacementNode = SyntaxFactory.BinaryExpression(SyntaxKind.EqualsExpression, node.Left, node.Right); - replacementNode = replacementNode.WithOperatorToken(replacementNode.OperatorToken.WithTriviaFrom(node.OperatorToken)); + var replacementNode = SyntaxFactory.BinaryExpression(SyntaxKind.EqualsExpression, node.Left.WithCleanTrivia(), node.Right.WithCleanTrivia()); + replacementNode = replacementNode.WithOperatorToken(replacementNode.OperatorToken.WithCleanTriviaFrom(node.OperatorToken)); return new Mutation { diff --git a/src/Stryker.Core/Stryker.Core/Mutators/BinaryPatternMutator.cs b/src/Stryker.Core/Stryker.Core/Mutators/BinaryPatternMutator.cs index c227756fe..e64c40e46 100644 --- a/src/Stryker.Core/Stryker.Core/Mutators/BinaryPatternMutator.cs +++ b/src/Stryker.Core/Stryker.Core/Mutators/BinaryPatternMutator.cs @@ -1,9 +1,10 @@ -using System.Collections.Generic; +using System.Collections.Generic; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Stryker.Abstractions.Mutants; using Stryker.Abstractions.Mutators; +using Stryker.Core.Helpers; namespace Stryker.Core.Mutators; @@ -27,8 +28,8 @@ public override IEnumerable ApplyMutations(BinaryPatternSyntax node, S foreach (var mutation in mutations) { // can't use the update method here, because roslyn implementation is broken - var replacementNode = SyntaxFactory.BinaryPattern(mutation, node.Left, node.Right); - replacementNode = replacementNode.WithOperatorToken(replacementNode.OperatorToken.WithTriviaFrom(node.OperatorToken)); + var replacementNode = SyntaxFactory.BinaryPattern(mutation, node.Left.WithCleanTrivia(), node.Right.WithCleanTrivia()); + replacementNode = replacementNode.WithOperatorToken(replacementNode.OperatorToken.WithCleanTriviaFrom(node.OperatorToken)); yield return new() { OriginalNode = node, diff --git a/src/Stryker.Core/Stryker.Core/Mutators/BlockMutator.cs b/src/Stryker.Core/Stryker.Core/Mutators/BlockMutator.cs index 9e9c7055c..c3d22cd7c 100644 --- a/src/Stryker.Core/Stryker.Core/Mutators/BlockMutator.cs +++ b/src/Stryker.Core/Stryker.Core/Mutators/BlockMutator.cs @@ -9,7 +9,7 @@ namespace Stryker.Core.Mutators; -class BlockMutator : MutatorBase +public class BlockMutator : MutatorBase { private const string MutationName = "Block removal mutation"; @@ -27,7 +27,7 @@ public override IEnumerable ApplyMutations(BlockSyntax node, SemanticM yield return new Mutation { OriginalNode = node, - ReplacementNode = SyntaxFactory.Block(), + ReplacementNode = SyntaxFactory.Block().WithCleanTriviaFrom(node), DisplayName = MutationName, Type = Mutator.Block }; diff --git a/src/Stryker.Core/Stryker.Core/Mutators/BooleanMutator.cs b/src/Stryker.Core/Stryker.Core/Mutators/BooleanMutator.cs index 364a9a0f8..2e855c8f5 100644 --- a/src/Stryker.Core/Stryker.Core/Mutators/BooleanMutator.cs +++ b/src/Stryker.Core/Stryker.Core/Mutators/BooleanMutator.cs @@ -3,6 +3,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Stryker.Abstractions.Mutants; using Stryker.Abstractions.Mutators; +using Stryker.Core.Helpers; using System.Collections.Generic; namespace Stryker.Core.Mutators; @@ -17,7 +18,7 @@ public override IEnumerable ApplyMutations(LiteralExpressionSyntax nod yield return new Mutation() { OriginalNode = node, - ReplacementNode = SyntaxFactory.LiteralExpression(SyntaxKind.FalseLiteralExpression), + ReplacementNode = SyntaxFactory.LiteralExpression(SyntaxKind.FalseLiteralExpression).WithCleanTriviaFrom(node), DisplayName = "Boolean mutation", Type = Mutator.Boolean }; @@ -27,7 +28,7 @@ public override IEnumerable ApplyMutations(LiteralExpressionSyntax nod yield return new Mutation() { OriginalNode = node, - ReplacementNode = SyntaxFactory.LiteralExpression(SyntaxKind.TrueLiteralExpression), + ReplacementNode = SyntaxFactory.LiteralExpression(SyntaxKind.TrueLiteralExpression).WithCleanTriviaFrom(node), DisplayName = "Boolean mutation", Type = Mutator.Boolean }; diff --git a/src/Stryker.Core/Stryker.Core/Mutators/CheckedMutator.cs b/src/Stryker.Core/Stryker.Core/Mutators/CheckedMutator.cs index f9fdd3164..084c20461 100644 --- a/src/Stryker.Core/Stryker.Core/Mutators/CheckedMutator.cs +++ b/src/Stryker.Core/Stryker.Core/Mutators/CheckedMutator.cs @@ -3,6 +3,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Stryker.Abstractions.Mutants; using Stryker.Abstractions.Mutators; +using Stryker.Core.Helpers; using System.Collections.Generic; namespace Stryker.Core.Mutators; @@ -18,7 +19,7 @@ public override IEnumerable ApplyMutations(CheckedExpressionSyntax nod yield return new Mutation() { OriginalNode = node, - ReplacementNode = node.Expression, + ReplacementNode = node.Expression.WithCleanTrivia(), DisplayName = "Remove checked expression", Type = Mutator.Checked }; diff --git a/src/Stryker.Core/Stryker.Core/Mutators/CollectionExpressionMutator.cs b/src/Stryker.Core/Stryker.Core/Mutators/CollectionExpressionMutator.cs index d39b44bba..7a0f8c055 100644 --- a/src/Stryker.Core/Stryker.Core/Mutators/CollectionExpressionMutator.cs +++ b/src/Stryker.Core/Stryker.Core/Mutators/CollectionExpressionMutator.cs @@ -4,6 +4,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Stryker.Abstractions.Mutants; using Stryker.Abstractions.Mutators; +using Stryker.Core.Helpers; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; namespace Stryker.Core.Mutators; @@ -24,11 +25,10 @@ public override IEnumerable ApplyMutations(CollectionExpressionSyntax yield return new Mutation { OriginalNode = node, - ReplacementNode = - type is not null - ? CastExpression(ParseTypeName(type.ToMinimalDisplayString(semanticModel, node.SpanStart)), - node.WithElements([])) - : node.WithElements([]), + ReplacementNode = type is not null + ? CastExpression(ParseTypeName(type.ToMinimalDisplayString(semanticModel, node.SpanStart)), + node.WithCleanTrivia().WithElements([])) + : node.WithCleanTrivia().WithElements([]), DisplayName = "Collection expression mutation", Type = Mutator.CollectionExpression }; diff --git a/src/Stryker.Core/Stryker.Core/Mutators/ConditionalExpressionMutator.cs b/src/Stryker.Core/Stryker.Core/Mutators/ConditionalExpressionMutator.cs index 105776c3f..2c7879d0c 100644 --- a/src/Stryker.Core/Stryker.Core/Mutators/ConditionalExpressionMutator.cs +++ b/src/Stryker.Core/Stryker.Core/Mutators/ConditionalExpressionMutator.cs @@ -5,6 +5,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Stryker.Abstractions.Mutants; using Stryker.Abstractions.Mutators; +using Stryker.Core.Helpers; namespace Stryker.Core.Mutators; @@ -28,8 +29,8 @@ public override IEnumerable ApplyMutations(ConditionalExpressionSyntax ReplacementNode = SyntaxFactory.ParenthesizedExpression( SyntaxFactory.ConditionalExpression( SyntaxFactory.LiteralExpression(SyntaxKind.TrueLiteralExpression), - node.WhenTrue, - node.WhenFalse + node.WhenTrue.WithCleanTrivia(), + node.WhenFalse.WithCleanTrivia() ) ) }; @@ -42,8 +43,8 @@ public override IEnumerable ApplyMutations(ConditionalExpressionSyntax ReplacementNode = SyntaxFactory.ParenthesizedExpression( SyntaxFactory.ConditionalExpression( SyntaxFactory.LiteralExpression(SyntaxKind.FalseLiteralExpression), - node.WhenTrue, - node.WhenFalse + node.WhenTrue.WithCleanTrivia(), + node.WhenFalse.WithCleanTrivia() ) ) }; diff --git a/src/Stryker.Core/Stryker.Core/Mutators/InitializerMutator.cs b/src/Stryker.Core/Stryker.Core/Mutators/InitializerMutator.cs index 9e7f95976..46790f390 100644 --- a/src/Stryker.Core/Stryker.Core/Mutators/InitializerMutator.cs +++ b/src/Stryker.Core/Stryker.Core/Mutators/InitializerMutator.cs @@ -3,6 +3,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Stryker.Abstractions.Mutants; using Stryker.Abstractions.Mutators; +using Stryker.Core.Helpers; using System.Collections.Generic; namespace Stryker.Core.Mutators; @@ -23,7 +24,7 @@ public override IEnumerable ApplyMutations(InitializerExpressionSyntax yield return new Mutation() { OriginalNode = node, - ReplacementNode = SyntaxFactory.InitializerExpression(SyntaxKind.ArrayInitializerExpression), + ReplacementNode = SyntaxFactory.InitializerExpression(SyntaxKind.ArrayInitializerExpression).WithCleanTriviaFrom(node), DisplayName = "Array initializer mutation", Type = Mutator.Initializer }; diff --git a/src/Stryker.Core/Stryker.Core/Mutators/InterpolatedStringMutator.cs b/src/Stryker.Core/Stryker.Core/Mutators/InterpolatedStringMutator.cs index 57fa2ba35..fd9830718 100644 --- a/src/Stryker.Core/Stryker.Core/Mutators/InterpolatedStringMutator.cs +++ b/src/Stryker.Core/Stryker.Core/Mutators/InterpolatedStringMutator.cs @@ -3,6 +3,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Stryker.Abstractions.Mutants; using Stryker.Abstractions.Mutators; +using Stryker.Core.Helpers; using System.Collections.Generic; namespace Stryker.Core.Mutators; @@ -18,7 +19,7 @@ public override IEnumerable ApplyMutations(InterpolatedStringExpressio yield return new Mutation { OriginalNode = node, - ReplacementNode = CreateEmptyInterpolatedString(), + ReplacementNode = CreateEmptyInterpolatedString().WithCleanTriviaFrom(node), DisplayName = @"String mutation", Type = Mutator.String }; diff --git a/src/Stryker.Core/Stryker.Core/Mutators/IsPatternExpressionMutator.cs b/src/Stryker.Core/Stryker.Core/Mutators/IsPatternExpressionMutator.cs index f0237a1d3..1105182a5 100644 --- a/src/Stryker.Core/Stryker.Core/Mutators/IsPatternExpressionMutator.cs +++ b/src/Stryker.Core/Stryker.Core/Mutators/IsPatternExpressionMutator.cs @@ -4,6 +4,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Stryker.Abstractions.Mutants; using Stryker.Abstractions.Mutators; +using Stryker.Core.Helpers; namespace Stryker.Core.Mutators; @@ -26,14 +27,14 @@ public override IEnumerable ApplyMutations(IsPatternExpressionSyntax n UnaryPatternSyntax notPattern => new Mutation { OriginalNode = node, - ReplacementNode = node.WithPattern(notPattern.Pattern), + ReplacementNode = node.WithCleanTrivia().WithPattern(notPattern.WithCleanTrivia().Pattern), Type = Mutator.Equality, DisplayName = "Equality mutation" }, _ => new Mutation { OriginalNode = node, - ReplacementNode = node.WithPattern(SyntaxFactory.UnaryPattern(node.Pattern.WithLeadingTrivia(SyntaxFactory.Space))), + ReplacementNode = node.WithCleanTrivia().WithPattern(SyntaxFactory.UnaryPattern(node.Pattern.WithLeadingTrivia(SyntaxFactory.Space).WithoutTrailingTrivia())), Type = Mutator.Equality, DisplayName = "Equality mutation" } diff --git a/src/Stryker.Core/Stryker.Core/Mutators/LinqMutator.cs b/src/Stryker.Core/Stryker.Core/Mutators/LinqMutator.cs index 886a500d7..07d5bf6d4 100644 --- a/src/Stryker.Core/Stryker.Core/Mutators/LinqMutator.cs +++ b/src/Stryker.Core/Stryker.Core/Mutators/LinqMutator.cs @@ -4,6 +4,7 @@ using Stryker.Abstractions; using Stryker.Abstractions.Mutants; using Stryker.Abstractions.Mutators; +using Stryker.Core.Helpers; using System; using System.Collections.Generic; @@ -112,7 +113,7 @@ public override IEnumerable ApplyMutations(ExpressionSyntax node, Sema $"Linq method mutation ({memberName}() to {SyntaxFactory.IdentifierName(replacementExpression.ToString())}())", OriginalNode = node, ReplacementNode = node.ReplaceNode(toReplace, - SyntaxFactory.IdentifierName(replacementExpression.ToString())), + SyntaxFactory.IdentifierName(replacementExpression.ToString())).WithCleanTrivia(), Type = Mutator.Linq }; } diff --git a/src/Stryker.Core/Stryker.Core/Mutators/MathMutator.cs b/src/Stryker.Core/Stryker.Core/Mutators/MathMutator.cs index 81ba3ace4..5c6cb533e 100644 --- a/src/Stryker.Core/Stryker.Core/Mutators/MathMutator.cs +++ b/src/Stryker.Core/Stryker.Core/Mutators/MathMutator.cs @@ -1,11 +1,11 @@ using System; using System.Collections.Generic; -using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Stryker.Abstractions.Mutants; using Stryker.Abstractions.Mutators; +using Stryker.Core.Helpers; namespace Stryker.Core.Mutators; @@ -19,29 +19,29 @@ public class MathMutator : MutatorBase static MathMutator() => KindsToMutate = new() { - [MathExpression.Acos] = new[] { MathExpression.Acosh, MathExpression.Asin, MathExpression.Atan }, - [MathExpression.Acosh] = new[] { MathExpression.Acos, MathExpression.Asinh, MathExpression.Atanh }, - [MathExpression.Asin] = new[] { MathExpression.Asinh, MathExpression.Acos, MathExpression.Atan }, - [MathExpression.Asinh] = new[] { MathExpression.Asin, MathExpression.Acosh, MathExpression.Atanh }, - [MathExpression.Atan] = new[] { MathExpression.Atanh, MathExpression.Acos, MathExpression.Asin }, - [MathExpression.Atanh] = new[] { MathExpression.Atan, MathExpression.Acosh, MathExpression.Asinh }, - [MathExpression.BitDecrement] = new[] { MathExpression.BitIncrement }, - [MathExpression.BitIncrement] = new[] { MathExpression.BitDecrement }, - [MathExpression.Ceiling] = new[] { MathExpression.Floor }, - [MathExpression.Cos] = new[] { MathExpression.Cosh, MathExpression.Sin, MathExpression.Tan }, - [MathExpression.Cosh] = new[] { MathExpression.Cos, MathExpression.Sinh, MathExpression.Tanh }, - [MathExpression.Exp] = new[] { MathExpression.Log }, - [MathExpression.Floor] = new[] { MathExpression.Ceiling }, - [MathExpression.Log] = new[] { MathExpression.Exp, MathExpression.Pow }, - [MathExpression.MaxMagnitude] = new[] { MathExpression.MinMagnitude }, - [MathExpression.MinMagnitude] = new[] { MathExpression.MaxMagnitude }, - [MathExpression.Pow] = new[] { MathExpression.Log }, - [MathExpression.ReciprocalEstimate] = new[] { MathExpression.ReciprocalSqrtEstimate }, - [MathExpression.ReciprocalSqrtEstimate] = new[] { MathExpression.ReciprocalEstimate, MathExpression.Sqrt }, - [MathExpression.Sin] = new[] { MathExpression.Sinh, MathExpression.Cos, MathExpression.Tan }, - [MathExpression.Sinh] = new[] { MathExpression.Sin, MathExpression.Cosh, MathExpression.Tanh }, - [MathExpression.Tan] = new[] { MathExpression.Tanh, MathExpression.Cos, MathExpression.Sin }, - [MathExpression.Tanh] = new[] { MathExpression.Tan, MathExpression.Cosh, MathExpression.Sinh } + [MathExpression.Acos] = [MathExpression.Acosh, MathExpression.Asin, MathExpression.Atan], + [MathExpression.Acosh] = [MathExpression.Acos, MathExpression.Asinh, MathExpression.Atanh], + [MathExpression.Asin] = [MathExpression.Asinh, MathExpression.Acos, MathExpression.Atan], + [MathExpression.Asinh] = [MathExpression.Asin, MathExpression.Acosh, MathExpression.Atanh], + [MathExpression.Atan] = [MathExpression.Atanh, MathExpression.Acos, MathExpression.Asin], + [MathExpression.Atanh] = [MathExpression.Atan, MathExpression.Acosh, MathExpression.Asinh], + [MathExpression.BitDecrement] = [MathExpression.BitIncrement], + [MathExpression.BitIncrement] = [MathExpression.BitDecrement], + [MathExpression.Ceiling] = [MathExpression.Floor], + [MathExpression.Cos] = [MathExpression.Cosh, MathExpression.Sin, MathExpression.Tan], + [MathExpression.Cosh] = [MathExpression.Cos, MathExpression.Sinh, MathExpression.Tanh], + [MathExpression.Exp] = [MathExpression.Log], + [MathExpression.Floor] = [MathExpression.Ceiling], + [MathExpression.Log] = [MathExpression.Exp, MathExpression.Pow], + [MathExpression.MaxMagnitude] = [MathExpression.MinMagnitude], + [MathExpression.MinMagnitude] = [MathExpression.MaxMagnitude], + [MathExpression.Pow] = [MathExpression.Log], + [MathExpression.ReciprocalEstimate] = [MathExpression.ReciprocalSqrtEstimate], + [MathExpression.ReciprocalSqrtEstimate] = [MathExpression.ReciprocalEstimate, MathExpression.Sqrt], + [MathExpression.Sin] = [MathExpression.Sinh, MathExpression.Cos, MathExpression.Tan], + [MathExpression.Sinh] = [MathExpression.Sin, MathExpression.Cosh, MathExpression.Tanh], + [MathExpression.Tan] = [MathExpression.Tanh, MathExpression.Cos, MathExpression.Sin], + [MathExpression.Tanh] = [MathExpression.Tan, MathExpression.Cosh, MathExpression.Sinh] }; /// Apply mutations to an @@ -49,7 +49,7 @@ public class MathMutator : MutatorBase { MemberAccessExpressionSyntax memberAccess => ApplyMutationsToMemberCall(node, memberAccess), IdentifierNameSyntax methodName => ApplyMutationsToDirectCall(node, methodName), - _ => Enumerable.Empty() + _ => [] }; private static IEnumerable ApplyMutationsToMemberCall(InvocationExpressionSyntax node, MemberAccessExpressionSyntax memberAccessExpressionSyntax) @@ -87,7 +87,7 @@ private static IEnumerable ApplyMutationsToMethod(InvocationExpression $"Math method mutation ({method.Identifier.ValueText}() to {SyntaxFactory.IdentifierName(replacementExpression.ToString())}())", OriginalNode = original, ReplacementNode = original.ReplaceNode(method, - SyntaxFactory.IdentifierName(replacementExpression.ToString())), + SyntaxFactory.IdentifierName(replacementExpression.ToString())).WithCleanTrivia(), Type = Mutator.Math }; } diff --git a/src/Stryker.Core/Stryker.Core/Mutators/NegateConditionMutator.cs b/src/Stryker.Core/Stryker.Core/Mutators/NegateConditionMutator.cs index ed3c486e2..dcff85f34 100644 --- a/src/Stryker.Core/Stryker.Core/Mutators/NegateConditionMutator.cs +++ b/src/Stryker.Core/Stryker.Core/Mutators/NegateConditionMutator.cs @@ -3,6 +3,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Stryker.Abstractions.Mutants; using Stryker.Abstractions.Mutators; +using Stryker.Core.Helpers; using System.Collections.Generic; namespace Stryker.Core.Mutators; @@ -47,7 +48,7 @@ public override IEnumerable ApplyMutations(ExpressionSyntax node, Sema yield return new Mutation() { OriginalNode = node, - ReplacementNode = replacement, + ReplacementNode = replacement.WithCleanTriviaFrom(node), DisplayName = "Negate expression", Type = Mutator.Boolean }; @@ -72,7 +73,5 @@ private static bool WillBeMutatedByOtherMutators(ExpressionSyntax node) } private static PrefixUnaryExpressionSyntax NegateCondition(ExpressionSyntax expressionSyntax) - { - return SyntaxFactory.PrefixUnaryExpression(SyntaxKind.LogicalNotExpression, SyntaxFactory.ParenthesizedExpression(expressionSyntax)); - } + => SyntaxFactory.PrefixUnaryExpression(SyntaxKind.LogicalNotExpression, SyntaxFactory.ParenthesizedExpression(expressionSyntax.WithCleanTrivia())); } diff --git a/src/Stryker.Core/Stryker.Core/Mutators/NullCoalescingExpressionMutator.cs b/src/Stryker.Core/Stryker.Core/Mutators/NullCoalescingExpressionMutator.cs index a8ba3c75c..0544cae8d 100644 --- a/src/Stryker.Core/Stryker.Core/Mutators/NullCoalescingExpressionMutator.cs +++ b/src/Stryker.Core/Stryker.Core/Mutators/NullCoalescingExpressionMutator.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using Microsoft.CodeAnalysis; using Stryker.Abstractions.Mutators; +using Stryker.Core.Helpers; namespace Stryker.Core.Mutators; @@ -29,7 +30,7 @@ public override IEnumerable ApplyMutations(BinaryExpressionSyntax node yield return new Mutation { OriginalNode = node, - ReplacementNode = node.WithLeft(node.Right).WithRight(node.Left), + ReplacementNode = node.WithLeft(node.Right).WithRight(node.Left).WithCleanTrivia(), DisplayName = "Null coalescing mutation (left to right)", Type = Mutator.NullCoalescing }; @@ -38,7 +39,7 @@ public override IEnumerable ApplyMutations(BinaryExpressionSyntax node yield return new Mutation { OriginalNode = node, - ReplacementNode = node.Right, + ReplacementNode = node.Right.WithCleanTrivia(), DisplayName = "Null coalescing mutation (remove left)", Type = Mutator.NullCoalescing }; @@ -50,7 +51,7 @@ public override IEnumerable ApplyMutations(BinaryExpressionSyntax node yield return new Mutation { OriginalNode = node, - ReplacementNode = node.Left, + ReplacementNode = node.Left.WithCleanTrivia(), DisplayName = $"Null coalescing mutation (remove right)", Type = Mutator.NullCoalescing }; diff --git a/src/Stryker.Core/Stryker.Core/Mutators/ObjectCreationMutator.cs b/src/Stryker.Core/Stryker.Core/Mutators/ObjectCreationMutator.cs index bdb7dbd90..2537f7517 100644 --- a/src/Stryker.Core/Stryker.Core/Mutators/ObjectCreationMutator.cs +++ b/src/Stryker.Core/Stryker.Core/Mutators/ObjectCreationMutator.cs @@ -3,6 +3,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Stryker.Abstractions.Mutants; using Stryker.Abstractions.Mutators; +using Stryker.Core.Helpers; using System.Collections.Generic; namespace Stryker.Core.Mutators; @@ -18,7 +19,7 @@ public override IEnumerable ApplyMutations(ObjectCreationExpressionSyn yield return new Mutation() { OriginalNode = node, - ReplacementNode = node.ReplaceNode(node.Initializer, SyntaxFactory.InitializerExpression(SyntaxKind.CollectionInitializerExpression)), + ReplacementNode = node.ReplaceNode(node.Initializer, SyntaxFactory.InitializerExpression(SyntaxKind.CollectionInitializerExpression)).WithCleanTrivia(), DisplayName = "Collection initializer mutation", Type = Mutator.Initializer }; @@ -28,7 +29,7 @@ public override IEnumerable ApplyMutations(ObjectCreationExpressionSyn yield return new Mutation() { OriginalNode = node, - ReplacementNode = node.ReplaceNode(node.Initializer, SyntaxFactory.InitializerExpression(SyntaxKind.ObjectInitializerExpression)), + ReplacementNode = node.ReplaceNode(node.Initializer, SyntaxFactory.InitializerExpression(SyntaxKind.ObjectInitializerExpression)).WithCleanTrivia(), DisplayName = "Object initializer mutation", Type = Mutator.Initializer, }; diff --git a/src/Stryker.Core/Stryker.Core/Mutators/PostfixUnaryMutator.cs b/src/Stryker.Core/Stryker.Core/Mutators/PostfixUnaryMutator.cs index ce9d650f6..4cca6a491 100644 --- a/src/Stryker.Core/Stryker.Core/Mutators/PostfixUnaryMutator.cs +++ b/src/Stryker.Core/Stryker.Core/Mutators/PostfixUnaryMutator.cs @@ -3,6 +3,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Stryker.Abstractions.Mutants; using Stryker.Abstractions.Mutators; +using Stryker.Core.Helpers; using System.Collections.Generic; namespace Stryker.Core.Mutators; @@ -31,7 +32,7 @@ public override IEnumerable ApplyMutations(PostfixUnaryExpressionSynta yield return new Mutation { OriginalNode = node, - ReplacementNode = SyntaxFactory.PostfixUnaryExpression(newKind, node.Operand), + ReplacementNode = SyntaxFactory.PostfixUnaryExpression(newKind, node.Operand.WithCleanTrivia()), DisplayName = $"{unaryKind} to {newKind} mutation", Type = Mutator.Update }; diff --git a/src/Stryker.Core/Stryker.Core/Mutators/PrefixUnaryMutator.cs b/src/Stryker.Core/Stryker.Core/Mutators/PrefixUnaryMutator.cs index a5b72387b..754ade229 100644 --- a/src/Stryker.Core/Stryker.Core/Mutators/PrefixUnaryMutator.cs +++ b/src/Stryker.Core/Stryker.Core/Mutators/PrefixUnaryMutator.cs @@ -3,6 +3,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Stryker.Abstractions.Mutants; using Stryker.Abstractions.Mutators; +using Stryker.Core.Helpers; using System.Collections.Generic; namespace Stryker.Core.Mutators; @@ -33,7 +34,7 @@ public override IEnumerable ApplyMutations(PrefixUnaryExpressionSyntax yield return new Mutation { OriginalNode = node, - ReplacementNode = SyntaxFactory.PrefixUnaryExpression(oppositeKind, node.Operand), + ReplacementNode = SyntaxFactory.PrefixUnaryExpression(oppositeKind, node.Operand.WithCleanTrivia()), DisplayName = $"{unaryKind} to {oppositeKind} mutation", Type = unaryKind.ToString().StartsWith("Unary") ? Mutator.Unary : Mutator.Update }; @@ -43,7 +44,7 @@ public override IEnumerable ApplyMutations(PrefixUnaryExpressionSyntax yield return new Mutation { OriginalNode = node, - ReplacementNode = node.Operand, + ReplacementNode = node.Operand.WithCleanTrivia(), DisplayName = $"{unaryKind} to un-{unaryKind} mutation", Type = unaryKind.ToString().StartsWith("Logic") ? Mutator.Boolean : Mutator.Unary }; diff --git a/src/Stryker.Core/Stryker.Core/Mutators/RegexMutator.cs b/src/Stryker.Core/Stryker.Core/Mutators/RegexMutator.cs index 3e33c8feb..9a88eca64 100644 --- a/src/Stryker.Core/Stryker.Core/Mutators/RegexMutator.cs +++ b/src/Stryker.Core/Stryker.Core/Mutators/RegexMutator.cs @@ -5,6 +5,7 @@ using Stryker.Abstractions.Logging; using Stryker.Abstractions.Mutants; using Stryker.Abstractions.Mutators; +using Stryker.Core.Helpers; using Stryker.RegexMutators; using System; using System.Collections.Generic; @@ -37,7 +38,7 @@ public override IEnumerable ApplyMutations(ObjectCreationExpressionSyn var patternArgument = namedArgument ?? node.ArgumentList.Arguments.FirstOrDefault(); var patternExpression = patternArgument?.Expression; - if (patternExpression?.Kind() == SyntaxKind.StringLiteralExpression) + if (patternExpression!= null && patternExpression.IsAStringExpression()) { var currentValue = ((LiteralExpressionSyntax)patternExpression).Token.ValueText; var regexMutantOrchestrator = new RegexMutantOrchestrator(currentValue); diff --git a/src/Stryker.Core/Stryker.Core/Mutators/RelationalPatternMutator.cs b/src/Stryker.Core/Stryker.Core/Mutators/RelationalPatternMutator.cs index 04e4012ec..a40960f33 100644 --- a/src/Stryker.Core/Stryker.Core/Mutators/RelationalPatternMutator.cs +++ b/src/Stryker.Core/Stryker.Core/Mutators/RelationalPatternMutator.cs @@ -4,6 +4,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Stryker.Abstractions.Mutants; using Stryker.Abstractions.Mutators; +using Stryker.Core.Helpers; namespace Stryker.Core.Mutators; @@ -31,7 +32,7 @@ public override IEnumerable ApplyMutations(RelationalPatternSyntax nod yield return new() { OriginalNode = node, - ReplacementNode = node.WithOperatorToken(SyntaxFactory.Token(mutation).WithTriviaFrom(node.OperatorToken)), + ReplacementNode = node.WithOperatorToken(SyntaxFactory.Token(mutation).WithCleanTriviaFrom(node.OperatorToken)).WithCleanTrivia(), DisplayName = "Equality mutation", Type = Mutator.Equality }; diff --git a/src/Stryker.Core/Stryker.Core/Mutators/StatementMutator.cs b/src/Stryker.Core/Stryker.Core/Mutators/StatementMutator.cs index c9d6de006..471abc527 100644 --- a/src/Stryker.Core/Stryker.Core/Mutators/StatementMutator.cs +++ b/src/Stryker.Core/Stryker.Core/Mutators/StatementMutator.cs @@ -12,8 +12,8 @@ public class StatementMutator : MutatorBase { public override MutationLevel MutationLevel => MutationLevel.Standard; - private static readonly HashSet AllowedSyntaxes = new HashSet() - { + private static readonly HashSet _allowedSyntaxes = + [ // SyntaxKind.EmptyStatement, // useless mutation // SyntaxKind.Block, // unitary mutation should prevail over blocks @@ -46,11 +46,11 @@ public class StatementMutator : MutatorBase SyntaxKind.YieldBreakStatement, SyntaxKind.ExpressionStatement, - }; + ]; public override IEnumerable ApplyMutations(StatementSyntax node, SemanticModel semanticModel) { - if (!AllowedSyntaxes.Contains(node.Kind())) + if (!_allowedSyntaxes.Contains(node.Kind())) { yield break; } @@ -70,7 +70,7 @@ public override IEnumerable ApplyMutations(StatementSyntax node, Seman } } - // flux-control inside switch-case may cause a compile error + // flow-control inside switch-case may cause a compile error if ((node is ReturnStatementSyntax || node is BreakStatementSyntax || node is ContinueStatementSyntax || @@ -85,7 +85,7 @@ node is GotoStatementSyntax || { // removing an assignment may cause a compile error if (expressionNode - .DescendantNodes(s => !(s is AnonymousFunctionExpressionSyntax)) + .DescendantNodes(s => s is not AnonymousFunctionExpressionSyntax) .OfType().Any()) { yield break; @@ -93,7 +93,7 @@ node is GotoStatementSyntax || // removing an out variable may cause a compile error if (expressionNode - .DescendantTokens(s => !(s is AnonymousFunctionExpressionSyntax)) + .DescendantTokens(s => s is not AnonymousFunctionExpressionSyntax) .Any(t => t.IsKind(SyntaxKind.OutKeyword))) { yield break; diff --git a/src/Stryker.Core/Stryker.Core/Mutators/StringEmptyMutator.cs b/src/Stryker.Core/Stryker.Core/Mutators/StringEmptyMutator.cs index 14f207a22..cd3260259 100644 --- a/src/Stryker.Core/Stryker.Core/Mutators/StringEmptyMutator.cs +++ b/src/Stryker.Core/Stryker.Core/Mutators/StringEmptyMutator.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -24,10 +23,10 @@ public class StringEmptyMutator : MutatorBase { MemberAccessExpressionSyntax memberAccess => ApplyMutations(memberAccess), InvocationExpressionSyntax invocation => ApplyMutations(invocation), - _ => Enumerable.Empty() + _ => [] }; - private IEnumerable ApplyMutations(MemberAccessExpressionSyntax node) + private static IEnumerable ApplyMutations(MemberAccessExpressionSyntax node) { if (IsAccessToStringPredefinedType(node.Expression) && node.Name.Identifier.ValueText == nameof(string.Empty)) @@ -64,11 +63,11 @@ private IEnumerable ApplyMutations(InvocationExpressionSyntax node) } } - private bool IsAccessToStringPredefinedType(ExpressionSyntax expression) => + private static bool IsAccessToStringPredefinedType(ExpressionSyntax expression) => expression is PredefinedTypeSyntax typeSyntax && typeSyntax.Keyword.ValueText == "string"; - private Mutation ApplyIsNullMutation(InvocationExpressionSyntax node) => new() + private static Mutation ApplyIsNullMutation(InvocationExpressionSyntax node) => new() { OriginalNode = node, ReplacementNode = @@ -81,7 +80,7 @@ expression is PredefinedTypeSyntax typeSyntax && Type = Mutator.String }; - private Mutation ApplyIsEmptyMutation(InvocationExpressionSyntax node) => new() + private static Mutation ApplyIsEmptyMutation(InvocationExpressionSyntax node) => new() { OriginalNode = node, ReplacementNode = SyntaxFactory.ParenthesizedExpression( @@ -93,7 +92,7 @@ expression is PredefinedTypeSyntax typeSyntax && Type = Mutator.String }; - private Mutation ApplyIsWhiteSpaceMutation(InvocationExpressionSyntax node) => new() + private static Mutation ApplyIsWhiteSpaceMutation(InvocationExpressionSyntax node) => new() { OriginalNode = node, ReplacementNode = diff --git a/src/Stryker.Core/Stryker.Core/Mutators/StringMethodMutator.cs b/src/Stryker.Core/Stryker.Core/Mutators/StringMethodMutator.cs index 9ddb185a5..1b45b263c 100644 --- a/src/Stryker.Core/Stryker.Core/Mutators/StringMethodMutator.cs +++ b/src/Stryker.Core/Stryker.Core/Mutators/StringMethodMutator.cs @@ -1,93 +1,13 @@ using System.Collections.Generic; -using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Stryker.Abstractions.Mutants; using Stryker.Abstractions.Mutators; +using Stryker.Core.Helpers; namespace Stryker.Core.Mutators; -public class StringMethodToConstantMutator : MutatorBase -{ - public override MutationLevel MutationLevel => MutationLevel.Advanced; - - public override IEnumerable ApplyMutations(InvocationExpressionSyntax node, SemanticModel model) - { - if (node is not { Expression: MemberAccessExpressionSyntax member } - || !IsOperationOnAString(member.Expression, model)) - { - yield break; - } - - var identifier = member.Name.Identifier.ValueText; - switch (identifier) - { - case "Trim" or "Substring": - yield return ApplyReplaceWithEmptyStringMutation(node, identifier); - break; - case "ElementAt" or "ElementAtOrDefault": - yield return ApplyReplaceWithCharMutation(node, identifier); - break; - default: - break; - } - } - - private static bool IsOperationOnAString(ExpressionSyntax expression, SemanticModel model) - { - if (model == null) - { - switch (expression) - { - case LiteralExpressionSyntax literal: - return literal.IsKind(SyntaxKind.StringLiteralExpression); - case InvocationExpressionSyntax: - return true; - case IdentifierNameSyntax: - { - return true; - var canBeValidSemanticModel = expression.Ancestors().Last() is CompilationUnitSyntax; - if (!canBeValidSemanticModel) - return false; - - var compilation = CSharpCompilation.Create(null) - .AddSyntaxTrees(expression.SyntaxTree); - model = compilation.GetSemanticModel(expression.SyntaxTree); - break; - } - } - } - - var typeInfo = model.GetTypeInfo(expression); - return typeInfo.Type?.SpecialType == SpecialType.System_String; - } - - private Mutation ApplyReplaceWithEmptyStringMutation(SyntaxNode node, string identifier) => - new() - { - OriginalNode = node, - ReplacementNode = SyntaxFactory.LiteralExpression( - SyntaxKind.StringLiteralExpression, - SyntaxFactory.Literal(string.Empty) - ), - DisplayName = $"String Method Mutation (Replace {identifier}() with Empty String)", - Type = Mutator.StringMethod - }; - - private Mutation ApplyReplaceWithCharMutation(SyntaxNode node, string identifier) => - new() - { - OriginalNode = node, - ReplacementNode = SyntaxFactory.LiteralExpression( - SyntaxKind.StringLiteralExpression, - SyntaxFactory.Literal(char.MinValue) - ), - DisplayName = $"String Method Mutation (Replace {identifier}() with '\\0' char)", - Type = Mutator.StringMethod - }; -} - /// Mutator Implementation for String method Mutations public class StringMethodMutator : MutatorBase { @@ -96,14 +16,13 @@ public class StringMethodMutator : MutatorBase public override IEnumerable ApplyMutations(MemberAccessExpressionSyntax member, SemanticModel model) { - if (!IsOperationOnAString(member.Expression, model)) - yield break; - var identifier = member.Name.Identifier.ValueText; - var replacement = GetReplacement(identifier); - if (replacement == null) + + if (replacement == null || !member.Expression.IsAStringExpression(model)) + { yield break; + } yield return ApplyReplaceMutation(member, identifier, replacement); } @@ -126,41 +45,11 @@ private static string GetReplacement(string identifier) => _ => null }; - private static bool IsOperationOnAString(ExpressionSyntax expression, SemanticModel model) - { - if (model == null) - { - switch (expression) - { - case LiteralExpressionSyntax literal: - { - return literal.IsKind(SyntaxKind.StringLiteralExpression); - } - case InvocationExpressionSyntax: - return true; - case IdentifierNameSyntax: - { - var canBeValidSemanticModel = expression.Ancestors().Last() is CompilationUnitSyntax; - if (!canBeValidSemanticModel) - return false; - - var compilation = CSharpCompilation.Create(null) - .AddSyntaxTrees(expression.SyntaxTree); - model = compilation.GetSemanticModel(expression.SyntaxTree); - break; - } - } - } - - var typeInfo = model.GetTypeInfo(expression); - return typeInfo.Type?.SpecialType == SpecialType.System_String; - } - - private Mutation ApplyReplaceMutation(MemberAccessExpressionSyntax node, string original, string replacement) => + private static Mutation ApplyReplaceMutation(MemberAccessExpressionSyntax node, string original, string replacement) => new() { OriginalNode = node, - ReplacementNode = node.WithName(SyntaxFactory.IdentifierName(replacement)), + ReplacementNode = node.WithName(SyntaxFactory.IdentifierName(replacement)).WithCleanTrivia(), DisplayName = $"String Method Mutation (Replace {original}() with {replacement}())", Type = Mutator.StringMethod }; diff --git a/src/Stryker.Core/Stryker.Core/Mutators/StringMethodToConstantMutator.cs b/src/Stryker.Core/Stryker.Core/Mutators/StringMethodToConstantMutator.cs new file mode 100644 index 000000000..b66a20043 --- /dev/null +++ b/src/Stryker.Core/Stryker.Core/Mutators/StringMethodToConstantMutator.cs @@ -0,0 +1,60 @@ +using System.Collections.Generic; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Stryker.Abstractions.Mutants; +using Stryker.Abstractions.Mutators; +using Stryker.Core.Helpers; + +namespace Stryker.Core.Mutators; + +public class StringMethodToConstantMutator : MutatorBase +{ + public override MutationLevel MutationLevel => MutationLevel.Advanced; + + public override IEnumerable ApplyMutations(InvocationExpressionSyntax node, SemanticModel model) + { + if (node is not { Expression: MemberAccessExpressionSyntax member } + || (model != null && !member.Expression.IsAStringExpression(model))) + { + yield break; + } + + var identifier = member.Name.Identifier.ValueText; + switch (identifier) + { + case "Trim" or "Substring": + yield return ApplyReplaceWithEmptyStringMutation(node, identifier); + break; + case "ElementAt" or "ElementAtOrDefault": + yield return ApplyReplaceWithCharMutation(node, identifier); + break; + default: + break; + } + } + + private static Mutation ApplyReplaceWithEmptyStringMutation(SyntaxNode node, string identifier) => + new() + { + OriginalNode = node, + ReplacementNode = SyntaxFactory.LiteralExpression( + SyntaxKind.StringLiteralExpression, + SyntaxFactory.Literal(string.Empty) + ), + DisplayName = $"String Method Mutation (Replace {identifier}() with Empty String)", + Type = Mutator.StringMethod + }; + + private static Mutation ApplyReplaceWithCharMutation(SyntaxNode node, string identifier) => + new() + { + OriginalNode = node, + ReplacementNode = SyntaxFactory.LiteralExpression( + SyntaxKind.StringLiteralExpression, + SyntaxFactory.Literal(char.MinValue) + ), + DisplayName = $"String Method Mutation (Replace {identifier}() with '\\0' char)", + Type = Mutator.StringMethod + }; +} diff --git a/src/Stryker.Core/Stryker.Core/Mutators/StringMutator.cs b/src/Stryker.Core/Stryker.Core/Mutators/StringMutator.cs index 95512fe42..9445b643a 100644 --- a/src/Stryker.Core/Stryker.Core/Mutators/StringMutator.cs +++ b/src/Stryker.Core/Stryker.Core/Mutators/StringMutator.cs @@ -3,6 +3,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Stryker.Abstractions.Mutants; using Stryker.Abstractions.Mutators; +using Stryker.Core.Helpers; using System; using System.Collections.Generic; using System.Text.RegularExpressions; @@ -18,7 +19,7 @@ public override IEnumerable ApplyMutations(LiteralExpressionSyntax nod // Get objectCreationSyntax to check if it contains a regex type. var root = node.Parent?.Parent?.Parent; - if (!IsSpecialType(root) && IsStringLiteral(node)) + if (!IsSpecialType(root) && node.IsAStringExpression()) { var currentValue = (string)node.Token.Value; var replacementValue = currentValue == "" ? "Stryker was here!" : ""; @@ -32,12 +33,6 @@ public override IEnumerable ApplyMutations(LiteralExpressionSyntax nod } } - private static bool IsStringLiteral(LiteralExpressionSyntax node) - { - var kind = node.Kind(); - return kind == SyntaxKind.StringLiteralExpression; - } - private static bool IsSpecialType(SyntaxNode root) => root switch { ObjectCreationExpressionSyntax ctor => IsCtorOfType(ctor, typeof(Regex)) || IsCtorOfType(ctor, typeof(Guid)),