diff --git a/docs/technical-reference/Mutation Orchestration Design.md b/docs/technical-reference/Mutation Orchestration Design.md index f592d931c..839b86910 100644 --- a/docs/technical-reference/Mutation Orchestration Design.md +++ b/docs/technical-reference/Mutation Orchestration Design.md @@ -262,8 +262,7 @@ This class is used for several constructs: - `AttributeListSyntax`: Attributes must be defined at compile time. - `ParameterListSyntax`: Parameters and default values must be known at compile time. - `EnumMemberSyntax`: Enumeration value must be known at compile time -- `RecursivePatternSyntax`: Pattern syntax must be known at compile time. -- `UsingDirectiveSyntax`: using directives are fixed and critical for compilation +- `UsingDirectiveSyntax`: using directives are fixed and critical for compilation - `FieldDeclarationSyntax` (only const fields): cannot modify const fields at run time. - `LocalDeclarationStatementSyntax` (only const): cannot modify constants at run time. 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/integrationtest/Validation/ValidationProject/ValidateStrykerResults.cs b/integrationtest/Validation/ValidationProject/ValidateStrykerResults.cs index 788657b66..4d4826ff4 100644 --- a/integrationtest/Validation/ValidationProject/ValidateStrykerResults.cs +++ b/integrationtest/Validation/ValidationProject/ValidateStrykerResults.cs @@ -83,7 +83,7 @@ public async Task CSharp_NetCore_SingleTestProject() var report = await JsonReportSerialization.DeserializeJsonReportAsync(strykerRunOutput); - CheckReportMutants(report, total: 601, ignored: 247, survived: 4, killed: 9, timeout: 2, nocoverage: 308); + CheckReportMutants(report, total: 606, ignored: 252, survived: 4, killed: 9, timeout: 2, nocoverage: 308); CheckReportTestCounts(report, total: 11); } @@ -122,7 +122,7 @@ public async Task CSharp_NetCore_WithTwoTestProjects() var report = await JsonReportSerialization.DeserializeJsonReportAsync(strykerRunOutput); - CheckReportMutants(report, total: 601, ignored: 105, survived: 5, killed: 11, timeout: 2, nocoverage: 447); + CheckReportMutants(report, total: 606, ignored: 105, survived: 5, killed: 11, timeout: 2, nocoverage: 452); CheckReportTestCounts(report, total: 21); } @@ -141,7 +141,7 @@ public async Task CSharp_NetCore_SolutionRun() var report = await JsonReportSerialization.DeserializeJsonReportAsync(strykerRunOutput); - CheckReportMutants(report, total: 601, ignored: 247, survived: 4, killed: 9, timeout: 2, nocoverage: 308); + CheckReportMutants(report, total: 606, ignored: 252, survived: 4, killed: 9, timeout: 2, nocoverage: 308); CheckReportTestCounts(report, total: 23); } diff --git a/src/Stryker.Core/Stryker.Core.UnitTest/AssertExtensions.cs b/src/Stryker.Core/Stryker.Core.UnitTest/AssertExtensions.cs index 9e9214a36..fcc3d6e2c 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,21 +34,36 @@ public static void ShouldBeSemantically(this SyntaxTree actual, SyntaxTree expec if (!isSame) { - // 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++) + var diff = ScanDiff(actual.GetRoot(), expected.GetRoot()); + + Console.WriteLine(string.Join(Environment.NewLine, diff)); + + } + } + + 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])) { - 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}"); - } - if (actualLines[i] != expectedLines[i]) - { - isSame.ShouldBeTrue($"AST's are not equivalent. Line[{i + 1}]{Environment.NewLine}actual:{actualLines[i]}{Environment.NewLine}expect:{expectedLines[i]}{Environment.NewLine}Actual(full):{Environment.NewLine}{actual}{Environment.NewLine}, expected:{Environment.NewLine}{expected}"); - } + 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 65b752315..988a7ccaa 100644 --- a/src/Stryker.Core/Stryker.Core.UnitTest/Mutants/CsharpMutantOrchestratorTests.cs +++ b/src/Stryker.Core/Stryker.Core.UnitTest/Mutants/CsharpMutantOrchestratorTests.cs @@ -1,13 +1,9 @@ -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; @@ -84,6 +80,27 @@ void MethodA() {if(StrykerNamespace.MutantControl.IsActive(0)){}else{ ShouldMutateSourceToExpected(source, expected); } + + + [TestMethod] + public void ShouldMutatePatterns() + { + + var source = @"public void Test() + { + Console.WriteLine(new[] { 1, 2, 3, 4 } is [>= 0, .., 2 or 4]); + } + "; + var expected = @"public void Test() +{ if(StrykerNamespace.MutantControl.IsActive(0)) {}else{ + if(StrykerNamespace.MutantControl.IsActive(1)){;}else{Console.WriteLine((StrykerNamespace.MutantControl.IsActive(2)?new[] { 1, 2, 3, 4 } is not [>= 0, .., 2 or 4]:(StrykerNamespace.MutantControl.IsActive(5)?new[] { 1, 2, 3, 4 } is [>= 0, .., 2 and 4]:(StrykerNamespace.MutantControl.IsActive(4)?new[] { 1, 2, 3, 4 } is [< 0, .., 2 or 4]:(StrykerNamespace.MutantControl.IsActive(3)?new[] { 1, 2, 3, 4 } is [> 0, .., 2 or 4]:new[] { 1, 2, 3, 4 } is [>= 0, .., 2 or 4])))));} + } +} + "; + ShouldMutateSourceInClassToExpected(source, expected); + + } + [TestMethod] public void ShouldMutateBlockStatements() { @@ -921,45 +938,6 @@ public void ShouldMutateIncrementStatementWithIfStatement() ShouldMutateSourceInClassToExpected(source, expected); } - [TestMethod] - public void ShouldNotMutateIfDisabledByComment() - { - var source = @"public void SomeMethod() { - var x = 0; -// Stryker disable all - x++; - x/=2; -}"; - var expected = @"public void SomeMethod() {if(StrykerNamespace.MutantControl.IsActive(0)){}else{ - var x = 0; -// Stryker disable all - x++; - x/=2; -}}"; - - ShouldMutateSourceInClassToExpected(source, expected); - - source = @"public void SomeMethod() { - var x = 0; - { - // Stryker disable all - x++; - } - x/=2; -}"; - expected = @"public void SomeMethod() {if(StrykerNamespace.MutantControl.IsActive(4)){}else{ - var x = 0; -{if(StrykerNamespace.MutantControl.IsActive(5)){}else { - // Stryker disable all - x++; - } -}if(StrykerNamespace.MutantControl.IsActive(8)){ x*=2; -}else{ x/=2; -}}}"; - ShouldMutateSourceInClassToExpected(source, expected); - } - - [TestMethod] public void ShouldNotMutateTopLevelStatementsIfDisabledByComment() { var source = @" @@ -981,283 +959,6 @@ public void ShouldNotMutateTopLevelStatementsIfDisabledByComment() } - [TestMethod] - public void ShouldNotMutateIfDisabledByMultilineComment() - { - var source = @"public void SomeMethod() { - var x = 0; - if (condition && other) /* Stryker disable once all */ { - x++; - x*=2; - } - x/=2; -}"; - var expected = @"public void SomeMethod() {if(StrykerNamespace.MutantControl.IsActive(0)){}else{ - var x = 0; - if ((StrykerNamespace.MutantControl.IsActive(2)?!(condition && other):(StrykerNamespace.MutantControl.IsActive(1)?condition || other:condition && other))) /* Stryker disable once all */ { - x++; - x*=2; - } -if(StrykerNamespace.MutantControl.IsActive(7)){ x*=2; -}else{ x/=2; -}}}"; - ; - - ShouldMutateSourceInClassToExpected(source, expected); - - source = @"public void SomeMethod() { - var x = 0; - { /* Stryker disable all */ - x++; - } - x/=2; -}"; - expected = @"public void SomeMethod() {if(StrykerNamespace.MutantControl.IsActive(8)){}else{ - var x = 0; -{if(StrykerNamespace.MutantControl.IsActive(9)){}else{ /* Stryker disable all */ - x++; - } -}if(StrykerNamespace.MutantControl.IsActive(12)){ x*=2; -}else{ x/=2; -}}}"; - - ShouldMutateSourceInClassToExpected(source, expected); - } - - [TestMethod] - [DataRow("/* Stryker disable once all*/ x++;", "/* Stryker disable once all*/ x++;")] - [DataRow("if (cond) /* Stryker disable once all*/ x++;", "if ((StrykerNamespace.MutantControl.IsActive(1)?!(cond):cond)) /* Stryker disable once all*/ x++;")] - [DataRow("if (cond);/* Stryker disable once all*/ else x++;", "if ((StrykerNamespace.MutantControl.IsActive(1)?!(cond):cond));/* Stryker disable once all*/ else x++;")] - [DataRow(@"if (cond) // Stryker disable once all -x++;", - @"if ((StrykerNamespace.MutantControl.IsActive(1)?!(cond):cond)) // Stryker disable once all -x++;")] - [DataRow("if (/* Stryker disable once all*/cond) x++;", "if (/* Stryker disable once all*/cond) if(StrykerNamespace.MutantControl.IsActive(2)){;}else{if(StrykerNamespace.MutantControl.IsActive(3)){x--;}else{x++;}}")] - - public void ShouldNotMutateDependingOnWhereMultilineCommentIs(string source, string expected) - { - // must call reset as MsTest reuse test instance on/datarow - var sourceTemplate = $@"public void SomeMethod() {{ - var x = 0; - {source} -}}"; - var expectedTemplate = $@"public void SomeMethod() {{if(StrykerNamespace.MutantControl.IsActive(0)){{}}else{{ - var x = 0; -{expected} - }}}}"; - - ShouldMutateSourceInClassToExpected(sourceTemplate, expectedTemplate); - } - - [TestMethod] - public void ShouldNotMutateIfDisabledByCommentOnStaticFields() - { - var source = @" - // Stryker disable all - static string x = ""test"";"; - - var expected = @" - // Stryker disable all - static string x = StrykerNamespace.MutantContext.TrackValue(()=>""test"");"; - - ShouldMutateSourceInClassToExpected(source, expected); - } - - [TestMethod] - public void ShouldMutateIfInvalidComment() - { - var source = @"public int SomeMethod()// Stryker disabel all -{x++;}"; - var expected = @"public int SomeMethod()// Stryker disabel all -{if(StrykerNamespace.MutantControl.IsActive(0)){}else{if(StrykerNamespace.MutantControl.IsActive(1)){;}else{if(StrykerNamespace.MutantControl.IsActive(2)){x--;}else{x++;}}}}"; - - ShouldMutateSourceInClassToExpected(source, expected); - - source = @"public int SomeMethod()// Stryker disable maths -{x++;}"; - expected = @"public int SomeMethod()// Stryker disable maths -{if(StrykerNamespace.MutantControl.IsActive(3)){}else{if(StrykerNamespace.MutantControl.IsActive(5)){x--;}else{x++;}}}}"; - - ShouldMutateSourceInClassToExpected(source, expected); - } - - [TestMethod] - public void ShouldOnlyUseFirstComment() - { - // enabling Stryker comment should have no impact - var source = @"public void SomeMethod() { - /* Stryker disable once all */ - // Stryker restore all - x++; - }"; - var expected = @"public void SomeMethod() {if(StrykerNamespace.MutantControl.IsActive(0)){}else{ - /* Stryker disable once all */ - // Stryker restore all - x++; - }}"; - - ShouldMutateSourceInClassToExpected(source, expected); - } - - [TestMethod] - public void ShouldNotMutateIfDisabledByCommentAtMethodLevel() - { - var source = @" -// Stryker disable all : testing -public void SomeMethod() - { - var x = 0; - x++; - x/=2; -}"; - var expected = @"// Stryker disable all : testing -public void SomeMethod() - { - var x = 0; - x++; - x/=2; -}"; - - ShouldMutateSourceInClassToExpected(source, expected); - source = @"public void SomeMethod() -{ - var x = 0; - { - // Stryker disable once all - x++; - } - x/=2; -}"; - expected = @"public void SomeMethod() -{if(StrykerNamespace.MutantControl.IsActive(4)){}else{ - var x = 0; -{if(StrykerNamespace.MutantControl.IsActive(5)){}else { - // Stryker disable once all - x++; - } -}if(StrykerNamespace.MutantControl.IsActive(8)){ x*=2; -}else{ x/=2; -}}}"; - ShouldMutateSourceInClassToExpected(source, expected); - } - - [TestMethod] - public void ShouldNotMutateOneLineIfDisabledByComment() - { - var source = @"public void SomeMethod() { - var x = 0; -// Stryker disable once all - x++; - x/=2; -}"; - var expected = @"public void SomeMethod() {if(StrykerNamespace.MutantControl.IsActive(0)){}else{ - var x = 0; -// Stryker disable once all - x++; -if(StrykerNamespace.MutantControl.IsActive(3)){ x*=2; -}else{ x/=2; -}}}"; - - ShouldMutateSourceInClassToExpected(source, expected); - } - - [TestMethod] - public void ShouldNotMutateOneLineIfDisabledAndEnabledByComment() - { - var source = @"public void SomeMethod() { - var x = 0; -// Stryker disable all - x++; -// Stryker restore all - x/=2; -}"; - var expected = @"public void SomeMethod() {if(StrykerNamespace.MutantControl.IsActive(0)){}else{ - var x = 0; -// Stryker disable all - x++; -if(StrykerNamespace.MutantControl.IsActive(3)){// Stryker restore all - x*=2; -}else{// Stryker restore all - x/=2; -}}}"; - - ShouldMutateSourceInClassToExpected(source, expected); - } - - [TestMethod] - public void ShouldNotMutateOneLineIfDisabledOnce() - { - var source = @"public void SomeMethod() { - var x = 0; -// Stryker disable once all - x++; - x/=2; -}"; - var expected = @"public void SomeMethod() {if(StrykerNamespace.MutantControl.IsActive(0)){}else{ - var x = 0; -// Stryker disable Update , Statement : comment - x++; -if(StrykerNamespace.MutantControl.IsActive(3)){// Stryker restore all - x*=2; -}else{// Stryker restore all - x/=2; -}}}"; - - ShouldMutateSourceInClassToExpected(source, expected); - } - - [TestMethod] - public void ShouldNotMutateOneLineIfSpecificMutatorDisabled() - { - var source = @"public void SomeMethod() { - var x = 0; -// Stryker disable Update , Statement : comment - x++; -// Stryker restore all - x/=2; -}"; - var expected = @"public void SomeMethod() {if(StrykerNamespace.MutantControl.IsActive(0)){}else{ - var x = 0; -// Stryker disable Update , Statement : comment - x++; -if(StrykerNamespace.MutantControl.IsActive(3)){// Stryker restore all - x*=2; -}else{// Stryker restore all - x/=2; -}}}"; - - ShouldMutateSourceInClassToExpected(source, expected); - - Target.Mutants.Count.ShouldBe(4); - Target.Mutants.ElementAt(0).ResultStatus.ShouldBe(MutantStatus.Pending); - Target.Mutants.ElementAt(1).ResultStatus.ShouldBe(MutantStatus.Ignored); - Target.Mutants.ElementAt(1).ResultStatusReason.ShouldBe("comment"); - Target.Mutants.ElementAt(2).ResultStatus.ShouldBe(MutantStatus.Ignored); - Target.Mutants.ElementAt(2).ResultStatusReason.ShouldBe("comment"); - Target.Mutants.ElementAt(3).ResultStatus.ShouldBe(MutantStatus.Pending); - } - - [TestMethod] - public void ShouldNotMutateASubExpressionIfDisabledByComment() - { - var source = @"public void SomeMethod() { - var x = 0; - x/= -// Stryker disable once all -x +2; -}"; - var expected = @"public void SomeMethod() {if(StrykerNamespace.MutantControl.IsActive(0)){}else{ - var x = 0; -if(StrykerNamespace.MutantControl.IsActive(1)){ x*=// Stryker disable once all -x +2; -}else{ x/= -// Stryker disable once all -x +2; -}}}"; - - ShouldMutateSourceInClassToExpected(source, expected); - } - [TestMethod] public void ShouldMutateChainedMutations() { @@ -1566,9 +1267,11 @@ public void ShouldNotAddReturnDefaultToAsyncTaskMethods() ; }"; var expected = @"public async Task TestMethod() -{if(StrykerNamespace.MutantControl.IsActive(0)){}else{ +{if(StrykerNamespace.MutantControl.IsActive(0)){} +else{ ; -}}"; +} +}"; ShouldMutateSourceInClassToExpected(source, expected); } @@ -1580,9 +1283,11 @@ public void ShouldNotAddReturnDefaultToMethodsWithReturnTypeVoid() ; }"; var expected = @"void TestMethod() -{if(StrykerNamespace.MutantControl.IsActive(0)){}else{ +{if(StrykerNamespace.MutantControl.IsActive(0)){}else +{ ; -}}"; +} +}"; ShouldMutateSourceInClassToExpected(source, expected); } @@ -1912,6 +1617,7 @@ static string Value(string text) => ShouldMutateSourceInClassToExpected(source, expected); } + [TestMethod] public void ShouldMutateSubStringMethod() { @@ -1939,10 +1645,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)))))); """; @@ -1980,7 +1686,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); } @@ -2004,7 +1710,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]); } } } @@ -2033,7 +1739,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 ]); } } } @@ -2057,7 +1763,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"]); } } """; @@ -2087,14 +1793,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) => @@ -2104,8 +1808,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])); } } """; @@ -2148,7 +1851,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 ]); @@ -2177,14 +1880,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"]); } } @@ -2210,7 +1913,7 @@ public void M() { if (StrykerNamespace.MutantControl.IsActive(1)) { ; } else { - Iter((StrykerNamespace.MutantControl.IsActive(2) ? [] : [ 1 ])); + Iter((StrykerNamespace.MutantControl.IsActive(2)?(IList)[]:[1])); } } } @@ -2225,7 +1928,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); } @@ -2235,7 +1938,32 @@ 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 !DEBUG +if(StrykerNamespace.MutantControl.IsActive(1)){;}else{if(StrykerNamespace.MutantControl.IsActive(2)){ + x--; +}else{ + 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 8918db842..c556dddf3 100644 --- a/src/Stryker.Core/Stryker.Core.UnitTest/Mutants/MutantOrchestratorTestsBase.cs +++ b/src/Stryker.Core/Stryker.Core.UnitTest/Mutants/MutantOrchestratorTestsBase.cs @@ -1,11 +1,13 @@ -using System.Text.RegularExpressions; using Microsoft.CodeAnalysis.CSharp; -using Stryker.Abstractions.Mutants; using Stryker.Abstractions.Mutators; using Stryker.Abstractions; using Stryker.Core.Mutants; using Stryker.Core.InjectedHelpers; using Stryker.Abstractions.Options; +using Microsoft.CodeAnalysis; +using System.Linq; +using System.Collections.Generic; +using System; namespace Stryker.Core.UnitTest.Mutants; @@ -25,34 +27,43 @@ public class MutantOrchestratorTestsBase : TestBase protected void ShouldMutateSourceToExpected(string actual, string expected) { - var actualNode = Target.Mutate(CSharpSyntaxTree.ParseText(actual), null); + var syntaxTree = CSharpSyntaxTree.ParseText(actual); + 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); + actualNode.ShouldNotContainErrors(); var expectedNode = CSharpSyntaxTree.ParseText(expected); actualNode.ShouldBeSemantically(expectedNode); - actualNode.ShouldNotContainErrors(); } 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 -{ - class TestClass - {" + actual + @"} -}"; +namespace StrykerNet.UnitTest.Mutants.TestResources;"; - expected = @"using System; -using System.Collections.Generic; -using System.Text; -namespace StrykerNet.UnitTest.Mutants.TestResources -{ - class TestClass - {" + expected + @"} -}"; + + actual = string.Format(@"{0} +class TestClass +{{ +{1} +}} +", initBlock, actual); + + expected = string.Format(@"{0} +class TestClass +{{ +{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 new file mode 100644 index 000000000..428df1f23 --- /dev/null +++ b/src/Stryker.Core/Stryker.Core.UnitTest/Mutants/StrykerCommentTests.cs @@ -0,0 +1,324 @@ +using System.Linq; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Shouldly; +using Stryker.Abstractions.Mutants; + +namespace Stryker.Core.UnitTest.Mutants; +internal class StrykerCommentTests : MutantOrchestratorTestsBase +{ + [TestMethod] + public void ShouldNotMutateIfDisabledByComment() + { + var source = @"public void SomeMethod() { + var x = 0; +// Stryker disable all + x++; + x/=2; +}"; + var expected = @"public void SomeMethod() {if(StrykerNamespace.MutantControl.IsActive(0)){}else{ + var x = 0; +// Stryker disable all + x++; + x/=2; +}}"; + + ShouldMutateSourceInClassToExpected(source, expected); + + source = @"public void SomeMethod() { + var x = 0; + { + // Stryker disable all + x++; + } + x/=2; +}"; + expected = @"public void SomeMethod() {if(StrykerNamespace.MutantControl.IsActive(4)){}else{ + var x = 0; +{if(StrykerNamespace.MutantControl.IsActive(5)){}else { + // Stryker disable all + x++; + } +}if(StrykerNamespace.MutantControl.IsActive(8)){ x*=2; +}else{ x/=2; +}}}"; + ShouldMutateSourceInClassToExpected(source, expected); + } + + [TestMethod] + public void ShouldNotMutateIfDisabledByMultilineComment() + { + var source = @"public void SomeMethod() { + var x = 0; + if (condition && other) /* Stryker disable once all */ { + x++; + x*=2; + } + x/=2; +}"; + var expected = @"public void SomeMethod() {if(StrykerNamespace.MutantControl.IsActive(0)){}else{ + var x = 0; + if ((StrykerNamespace.MutantControl.IsActive(2)?!(condition && other):(StrykerNamespace.MutantControl.IsActive(1)?condition || other:condition && other))) /* Stryker disable once all */ { + x++; + x*=2; + } +if(StrykerNamespace.MutantControl.IsActive(7)){ x*=2; +}else{ x/=2; +}}}"; + ; + + ShouldMutateSourceInClassToExpected(source, expected); + + source = @"public void SomeMethod() { + var x = 0; + { /* Stryker disable all */ + x++; + } + x/=2; +}"; + expected = @"public void SomeMethod() {if(StrykerNamespace.MutantControl.IsActive(8)){}else{ + var x = 0; +{if(StrykerNamespace.MutantControl.IsActive(9)){}else{ /* Stryker disable all */ + x++; + } +}if(StrykerNamespace.MutantControl.IsActive(12)){ x*=2; +}else{ x/=2; +}}}"; + + ShouldMutateSourceInClassToExpected(source, expected); + } + + [TestMethod] + [DataRow("/* Stryker disable once all*/ x++;", "/* Stryker disable once all*/ x++;")] + [DataRow("if (cond) /* Stryker disable once all*/ x++;", "if ((StrykerNamespace.MutantControl.IsActive(1)?!(cond):cond)) /* Stryker disable once all*/ x++;")] + [DataRow("if (cond);/* Stryker disable once all*/ else x++;", "if ((StrykerNamespace.MutantControl.IsActive(1)?!(cond):cond));/* Stryker disable once all*/ else x++;")] + [DataRow(@"if (cond) // Stryker disable once all +x++;", + @"if ((StrykerNamespace.MutantControl.IsActive(1)?!(cond):cond)) // Stryker disable once all +x++;")] + [DataRow("if (/* Stryker disable once all*/cond) x++;", "if (/* Stryker disable once all*/cond) if(StrykerNamespace.MutantControl.IsActive(2)){;}else{if(StrykerNamespace.MutantControl.IsActive(3)){x--;}else{x++;}}")] + + public void ShouldNotMutateDependingOnWhereMultilineCommentIs(string source, string expected) + { + // must call reset as MsTest reuse test instance on/datarow + var sourceTemplate = $@"public void SomeMethod() {{ + var x = 0; + {source} +}}"; + var expectedTemplate = $@"public void SomeMethod() {{if(StrykerNamespace.MutantControl.IsActive(0)){{}}else{{ + var x = 0; +{expected} + }}}}"; + + ShouldMutateSourceInClassToExpected(sourceTemplate, expectedTemplate); + } + + [TestMethod] + public void ShouldNotMutateIfDisabledByCommentOnStaticFields() + { + var source = @" + // Stryker disable all + static string x = ""test"";"; + + var expected = @" + // Stryker disable all + static string x = StrykerNamespace.MutantContext.TrackValue(()=>""test"");"; + + ShouldMutateSourceInClassToExpected(source, expected); + } + + [TestMethod] + public void ShouldMutateIfInvalidComment() + { + var source = @"public int SomeMethod()// Stryker disabel all +{x++;}"; + var expected = @"public int SomeMethod()// Stryker disabel all +{if(StrykerNamespace.MutantControl.IsActive(0)){}else{if(StrykerNamespace.MutantControl.IsActive(1)){;}else{if(StrykerNamespace.MutantControl.IsActive(2)){x--;}else{x++;}}}}"; + + ShouldMutateSourceInClassToExpected(source, expected); + + source = @"public int SomeMethod()// Stryker disable maths +{x++;}"; + expected = @"public int SomeMethod()// Stryker disable maths +{if(StrykerNamespace.MutantControl.IsActive(3)){}else{if(StrykerNamespace.MutantControl.IsActive(5)){x--;}else{x++;}}}}"; + + ShouldMutateSourceInClassToExpected(source, expected); + } + + [TestMethod] + public void ShouldOnlyUseFirstComment() + { + // enabling Stryker comment should have no impact + var source = @"public void SomeMethod() { + /* Stryker disable once all */ + // Stryker restore all + x++; + }"; + var expected = @"public void SomeMethod() {if(StrykerNamespace.MutantControl.IsActive(0)){}else{ + /* Stryker disable once all */ + // Stryker restore all + x++; + }}"; + + ShouldMutateSourceInClassToExpected(source, expected); + } + + [TestMethod] + public void ShouldNotMutateIfDisabledByCommentAtMethodLevel() + { + var source = @" +// Stryker disable all : testing +public void SomeMethod() + { + var x = 0; + x++; + x/=2; +}"; + var expected = @"// Stryker disable all : testing +public void SomeMethod() + { + var x = 0; + x++; + x/=2; +}"; + + ShouldMutateSourceInClassToExpected(source, expected); + source = @"public void SomeMethod() +{ + var x = 0; + { + // Stryker disable once all + x++; + } + x/=2; +}"; + expected = @"public void SomeMethod() +{if(StrykerNamespace.MutantControl.IsActive(4)){}else{ + var x = 0; +{if(StrykerNamespace.MutantControl.IsActive(5)){}else { + // Stryker disable once all + x++; + } +}if(StrykerNamespace.MutantControl.IsActive(8)){ x*=2; +}else{ x/=2; +}}}"; + ShouldMutateSourceInClassToExpected(source, expected); + } + + [TestMethod] + public void ShouldNotMutateOneLineIfDisabledByComment() + { + var source = @"public void SomeMethod() { + var x = 0; +// Stryker disable once all + x++; + x/=2; +}"; + var expected = @"public void SomeMethod() {if(StrykerNamespace.MutantControl.IsActive(0)){}else{ + var x = 0; +// Stryker disable once all + x++; +if(StrykerNamespace.MutantControl.IsActive(3)){ x*=2; +}else{ x/=2; +}}}"; + + ShouldMutateSourceInClassToExpected(source, expected); + } + + [TestMethod] + public void ShouldNotMutateOneLineIfDisabledAndEnabledByComment() + { + var source = @"public void SomeMethod() { + var x = 0; +// Stryker disable all + x++; +// Stryker restore all + x/=2; +}"; + var expected = @"public void SomeMethod() {if(StrykerNamespace.MutantControl.IsActive(0)){}else{ + var x = 0; +// Stryker disable all + x++; +if(StrykerNamespace.MutantControl.IsActive(3)){// Stryker restore all + x*=2; +}else{// Stryker restore all + x/=2; +}}}"; + + ShouldMutateSourceInClassToExpected(source, expected); + } + + [TestMethod] + public void ShouldNotMutateOneLineIfDisabledOnce() + { + var source = @"public void SomeMethod() { + var x = 0; +// Stryker disable once all + x++; + x/=2; +}"; + var expected = @"public void SomeMethod() {if(StrykerNamespace.MutantControl.IsActive(0)){}else{ + var x = 0; +// Stryker disable Update , Statement : comment + x++; +if(StrykerNamespace.MutantControl.IsActive(3)){// Stryker restore all + x*=2; +}else{// Stryker restore all + x/=2; +}}}"; + + ShouldMutateSourceInClassToExpected(source, expected); + } + + [TestMethod] + public void ShouldNotMutateOneLineIfSpecificMutatorDisabled() + { + var source = @"public void SomeMethod() { + var x = 0; +// Stryker disable Update , Statement : comment + x++; +// Stryker restore all + x/=2; +}"; + var expected = @"public void SomeMethod() {if(StrykerNamespace.MutantControl.IsActive(0)){}else{ + var x = 0; +// Stryker disable Update , Statement : comment + x++; +if(StrykerNamespace.MutantControl.IsActive(3)){// Stryker restore all + x*=2; +}else{// Stryker restore all + x/=2; +}}}"; + + ShouldMutateSourceInClassToExpected(source, expected); + + Target.Mutants.Count.ShouldBe(4); + Target.Mutants.ElementAt(0).ResultStatus.ShouldBe(MutantStatus.Pending); + Target.Mutants.ElementAt(1).ResultStatus.ShouldBe(MutantStatus.Ignored); + Target.Mutants.ElementAt(1).ResultStatusReason.ShouldBe("comment"); + Target.Mutants.ElementAt(2).ResultStatus.ShouldBe(MutantStatus.Ignored); + Target.Mutants.ElementAt(2).ResultStatusReason.ShouldBe("comment"); + Target.Mutants.ElementAt(3).ResultStatus.ShouldBe(MutantStatus.Pending); + } + + [TestMethod] + public void ShouldNotMutateASubExpressionIfDisabledByComment() + { + var source = @"public void SomeMethod() { + var x = 0; + x/= +// Stryker disable once all +x +2; +}"; + var expected = @"public void SomeMethod() {if(StrykerNamespace.MutantControl.IsActive(0)){}else{ + var x = 0; +if(StrykerNamespace.MutantControl.IsActive(1)){ x*=// Stryker disable once all +x +2; +}else{ x/= +// Stryker disable once all +x +2; +}}}"; + + ShouldMutateSourceInClassToExpected(source, expected); + } + +} diff --git a/src/Stryker.Core/Stryker.Core.UnitTest/Mutators/IsPatternExpressionMutatorTests.cs b/src/Stryker.Core/Stryker.Core.UnitTest/Mutators/IsPatternExpressionMutatorTests.cs index eda7142e1..a63342e0c 100644 --- a/src/Stryker.Core/Stryker.Core.UnitTest/Mutators/IsPatternExpressionMutatorTests.cs +++ b/src/Stryker.Core/Stryker.Core.UnitTest/Mutators/IsPatternExpressionMutatorTests.cs @@ -21,8 +21,8 @@ public void ShouldMutateIsToIsNot() var mutation = target.ApplyMutations(expression, null).First(); - mutation.OriginalNode.ShouldBeOfType(); - mutation.ReplacementNode.ShouldBeOfType(); + mutation.OriginalNode.ToString().ShouldBe("1 is 1"); + mutation.ReplacementNode.ToString().ShouldBe("1 is not 1"); mutation.DisplayName.ShouldBe("Equality mutation"); } @@ -35,8 +35,8 @@ public void ShouldMutateIsNotToIs() var mutation = target.ApplyMutations(expression, null).First(); - mutation.OriginalNode.ShouldBeOfType(); - mutation.ReplacementNode.ShouldBeOfType(); + mutation.OriginalNode.ToString().ShouldBe("1 is not 1"); + mutation.ReplacementNode.ToString().ShouldBe("1 is 1"); mutation.DisplayName.ShouldBe("Equality mutation"); } @@ -47,11 +47,11 @@ public void ShouldMutateIsNotToIs() [DataRow("<=", new[] { SyntaxKind.GreaterThanToken, SyntaxKind.LessThanToken })] public void ShouldMutateRelationalPattern(string @operator, SyntaxKind[] mutated) { - var target = new IsPatternExpressionMutator(); + var target = new RelationalPatternMutator(); - var expression = GenerateWithRelationalPattern(@operator); + var expression = GenerateWithRelationalPattern(@operator).DescendantNodes().OfType().First(); - var result = target.ApplyMutations(expression, null).Skip(1).ToList(); + var result = target.ApplyMutations(expression, null).ToList(); result.ForEach(mutation => { @@ -71,11 +71,11 @@ public void ShouldMutateRelationalPattern(string @operator, SyntaxKind[] mutated [DataRow("or", new[] { SyntaxKind.AndPattern })] public void ShouldMutateLogicalPattern(string @operator, SyntaxKind[] mutated) { - var target = new IsPatternExpressionMutator(); + var target = new BinaryPatternMutator(); var expression = GenerateWithBinaryPattern(@operator); - var result = target.ApplyMutations(expression, null).Skip(1).ToList(); + var result = target.ApplyMutations(expression, null).ToList(); result.ForEach(mutation => { @@ -112,7 +112,7 @@ class Program {{ static void Main(string[] args) {{ - var a = 1 is {(isNotPattern ? "not" : string.Empty)} 1; + var a = 1 is{(isNotPattern ? " not" : string.Empty)} 1; }} }} }}"); @@ -147,7 +147,7 @@ static void Main(string[] args) return isPatternExpression; } - private IsPatternExpressionSyntax GenerateWithBinaryPattern(string pattern) + private BinaryPatternSyntax GenerateWithBinaryPattern(string pattern) { var tree = CSharpSyntaxTree.ParseText($@" using System; @@ -164,7 +164,7 @@ static void Main(string[] args) }}"); var isPatternExpression = tree.GetRoot() .DescendantNodes() - .OfType() + .OfType() .Single(); return isPatternExpression; 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.UnitTest/Mutators/StringMethodMutatorTests.cs b/src/Stryker.Core/Stryker.Core.UnitTest/Mutators/StringMethodMutatorTests.cs index 9739cc82a..2d0e249c1 100644 --- a/src/Stryker.Core/Stryker.Core.UnitTest/Mutators/StringMethodMutatorTests.cs +++ b/src/Stryker.Core/Stryker.Core.UnitTest/Mutators/StringMethodMutatorTests.cs @@ -65,7 +65,7 @@ public void ShouldMutateStringMethods(string expression, string mutatedMethod, s { var (semanticModel, expressionSyntax) = CreateSemanticModelFromExpression(expression); var target = new StringMethodMutator(); - var result = target.ApplyMutations(expressionSyntax, semanticModel).ToList(); + var result = target.ApplyMutations((MemberAccessExpressionSyntax)expressionSyntax.Expression, semanticModel).ToList(); var mutation = result.ShouldHaveSingleItem(); @@ -83,7 +83,7 @@ public void ShouldMutateReplaceWithEmptyString(string methodName) { var expression = $"testString.{methodName}()"; var (semanticModel, expressionSyntax) = CreateSemanticModelFromExpression(expression); - var target = new StringMethodMutator(); + var target = new StringMethodToConstantMutator(); var result = target.ApplyMutations(expressionSyntax, semanticModel).ToList(); var mutation = result.ShouldHaveSingleItem(); @@ -101,7 +101,7 @@ public void ShouldMutateReplaceWithChar(string methodName) { var expression = $"testString.{methodName}()"; var (semanticModel, expressionSyntax) = CreateSemanticModelFromExpression(expression); - var target = new StringMethodMutator(); + var target = new StringMethodToConstantMutator(); var result = target.ApplyMutations(expressionSyntax, semanticModel).ToList(); var mutation = result.ShouldHaveSingleItem(); @@ -118,7 +118,7 @@ public void ShouldNotMutateWhenNotAString() var expression = (InvocationExpressionSyntax) SyntaxFactory.ParseExpression("Enumerable.Max(new[] { 1, 2, 3 })"); var target = new StringMethodMutator(); - var result = target.ApplyMutations(expression, null).ToList(); + var result = target.ApplyMutations((MemberAccessExpressionSyntax)expression.Expression, null).ToList(); result.ShouldBeEmpty(); } diff --git a/src/Stryker.Core/Stryker.Core.UnitTest/Mutators/SwitchExpressionMutatorTests.cs b/src/Stryker.Core/Stryker.Core.UnitTest/Mutators/SwitchExpressionMutatorTests.cs deleted file mode 100644 index 6bb452466..000000000 --- a/src/Stryker.Core/Stryker.Core.UnitTest/Mutators/SwitchExpressionMutatorTests.cs +++ /dev/null @@ -1,267 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Shouldly; -using Stryker.Core.Mutators; - -namespace Stryker.Core.UnitTest.Mutators; - -[TestClass] -public class SwitchExpressionMutatorTests : TestBase -{ - [TestMethod] - [DataRow(">", new[] { SyntaxKind.LessThanToken, SyntaxKind.GreaterThanEqualsToken })] - [DataRow("<", new[] { SyntaxKind.GreaterThanToken, SyntaxKind.LessThanEqualsToken })] - [DataRow(">=", new[] { SyntaxKind.GreaterThanToken, SyntaxKind.LessThanToken })] - [DataRow("<=", new[] { SyntaxKind.GreaterThanToken, SyntaxKind.LessThanToken })] - public void ShouldMutateRelationalPattern(string @operator, SyntaxKind[] mutated) - { - var target = new SwitchExpressionMutator(); - - var expression = GenerateWithRelationalPattern(@operator); - - var result = target.ApplyMutations(expression, null).ToList(); - - result.ForEach(mutation => - { - mutation.OriginalNode.ShouldBeOfType(); - mutation.ReplacementNode.ShouldBeOfType(); - mutation.DisplayName.ShouldBe($"Equality mutation"); - }); - - result - .Select(mutation => (RelationalPatternSyntax)mutation.ReplacementNode) - .Select(pattern => pattern.OperatorToken.Kind()) - .ShouldBe(mutated, true); - } - - [TestMethod] - [DataRow("and", new[] { SyntaxKind.OrPattern })] - [DataRow("or", new[] { SyntaxKind.AndPattern })] - public void ShouldMutateLogicalPattern(string @operator, SyntaxKind[] mutated) - { - var target = new SwitchExpressionMutator(); - - var expression = GenerateWithBinaryPattern(@operator); - - var result = target.ApplyMutations(expression, null).ToList(); - - result.ForEach(mutation => - { - mutation.OriginalNode.ShouldBeOfType(); - mutation.ReplacementNode.ShouldBeOfType(); - mutation.DisplayName.ShouldBe($"Logical mutation"); - }); - - result - .Select(mutation => (BinaryPatternSyntax)mutation.ReplacementNode) - .Select(pattern => pattern.Kind()) - .ShouldBe(mutated, true); - } - - [TestMethod] - [DynamicData(nameof(GenerateNotSupportedPatterns))] - public void ShouldNotMutateNotSupportedPatterns(SwitchExpressionSyntax expression) - { - var target = new SwitchExpressionMutator(); - - var result = target.ApplyMutations(expression, null).ToList(); - - result.ShouldBeEmpty(); - } - - private SwitchExpressionSyntax GenerateWithRelationalPattern(string @operator) - { - var tree = CSharpSyntaxTree.ParseText($@" -using System; - -namespace TestApplication -{{ - class Program - {{ - static void Main(string[] args) - {{ - var a = 1 switch - {{ - {@operator} 0 => 1 - }}; - }} - }} -}}"); - var switchExpression = tree.GetRoot() - .DescendantNodes() - .OfType() - .Single(); - - return switchExpression; - } - - private SwitchExpressionSyntax GenerateWithBinaryPattern(string pattern) - { - var tree = CSharpSyntaxTree.ParseText($@" -using System; - -namespace TestApplication -{{ - class Program - {{ - static void Main(string[] args) - {{ - var a = 1 switch - {{ - 1 {pattern} 2 => 1 - }}; - }} - }} -}}"); - var switchExpression = tree.GetRoot() - .DescendantNodes() - .OfType() - .Single(); - - return switchExpression; - } - - public static IEnumerable GenerateNotSupportedPatterns - { - get - { - SwitchExpressionSyntax GetExpressionFromTree(SyntaxTree tree) - { - return tree.GetRoot() - .DescendantNodes() - .OfType() - .Single(); - } - - yield return new[] - { - GetExpressionFromTree(CSharpSyntaxTree.ParseText($@" - using System; - - namespace TestApplication - {{ - class Program - {{ - static void Main(string[] args) - {{ - var a = 1 switch - {{ - 1 => 2 - }}; - }} - }} - }}" - )) - }; - - yield return new[] - { - GetExpressionFromTree(CSharpSyntaxTree.ParseText($@" - using System; - - namespace TestApplication - {{ - class Program - {{ - static void Main(string[] args) - {{ - var a = 1 switch - {{ - _ => 2 - }}; - }} - }} - }}" - )) - }; - - yield return new[] - { - GetExpressionFromTree(CSharpSyntaxTree.ParseText($@" - using System; - - namespace TestApplication - {{ - class Program - {{ - static void Main(string[] args) - {{ - var a = 1 switch - {{ - int b => 1 - }}; - }} - }} - }}" - )) - }; - - yield return new[] - { - GetExpressionFromTree(CSharpSyntaxTree.ParseText($@" - using System; - - namespace TestApplication - {{ - class Program - {{ - static void Main(string[] args) - {{ - var a = 1 switch - {{ - int => 1 - }}; - }} - }} - }}" - )) - }; - - yield return new[] - { - GetExpressionFromTree(CSharpSyntaxTree.ParseText($@" - using System; - - namespace TestApplication - {{ - class Program - {{ - static void Main(string[] args) - {{ - var a = ""test"" switch - {{ - {{ Length: 1 }} => 1 - }}; - }} - }} - }}" - )) - }; - - yield return new[] - { - GetExpressionFromTree(CSharpSyntaxTree.ParseText($@" - using System; - - namespace TestApplication - {{ - class Program - {{ - static void Main(string[] args) - {{ - var a = new[] {{ 1, 2 }} switch - {{ - [1, _] => 1 - }}; - }} - }} - }}" - )) - }; - } - } -} diff --git a/src/Stryker.Core/Stryker.Core/Helpers/RoslynHelper.cs b/src/Stryker.Core/Stryker.Core/Helpers/RoslynHelper.cs index da554217a..9a7eaa40b 100644 --- a/src/Stryker.Core/Stryker.Core/Helpers/RoslynHelper.cs +++ b/src/Stryker.Core/Stryker.Core/Helpers/RoslynHelper.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -10,13 +11,22 @@ namespace Stryker.Core.Helpers; internal static class RoslynHelper { /// - /// Check if an expression is a string. + /// Check if an expression is an explicit string expression. /// /// Expression to check /// true if it is a string public static bool IsAStringExpression(this ExpressionSyntax node) => - node.Kind() == SyntaxKind.StringLiteralExpression || - node.Kind() == SyntaxKind.InterpolatedStringExpression; + node.Kind() is SyntaxKind.StringLiteralExpression or + SyntaxKind.InterpolatedStringExpression; + + /// + /// Check if an expression is a string using the semantic model. + /// + /// Expression to check + /// Semantic model + /// true if it is a string + public static bool IsAStringExpression(this ExpressionSyntax node, SemanticModel model) => + node.IsAStringExpression() || model.GetTypeInfo(node).Type?.SpecialType == SpecialType.System_String; /// /// Check if an expression contains a declaration @@ -154,28 +164,19 @@ public static bool ScanChildStatements(this StatementSyntax syntax, Func s.ScanChildStatements(predicate, false)); - case LocalFunctionStatementSyntax: - return false; - case ForStatementSyntax forStatement: - return forStatement.Statement.ScanChildStatements(predicate, skipBlocks); - case WhileStatementSyntax whileStatement: - return whileStatement.Statement.ScanChildStatements(predicate, skipBlocks); - case DoStatementSyntax doStatement: - return doStatement.Statement.ScanChildStatements(predicate, skipBlocks); - case SwitchStatementSyntax switchStatement: - return switchStatement.Sections.SelectMany(s => s.Statements).Any(statement => statement.ScanChildStatements(predicate, skipBlocks)); - case IfStatementSyntax ifStatement: - if (ifStatement.Statement.ScanChildStatements(predicate, skipBlocks)) - return true; - return ifStatement.Else?.Statement.ScanChildStatements(predicate, skipBlocks) == true; - default: - return syntax.ChildNodes().Where(n => n is StatementSyntax).Cast() - .Any(s => s.ScanChildStatements(predicate, skipBlocks)); - } + BlockSyntax block => !skipBlocks && block.Statements.Any(s => s.ScanChildStatements(predicate, false)), + LocalFunctionStatementSyntax => false, + ForStatementSyntax forStatement => forStatement.Statement.ScanChildStatements(predicate, skipBlocks), + WhileStatementSyntax whileStatement => whileStatement.Statement.ScanChildStatements(predicate, skipBlocks), + DoStatementSyntax doStatement => doStatement.Statement.ScanChildStatements(predicate, skipBlocks), + SwitchStatementSyntax switchStatement => switchStatement.Sections.SelectMany(s => s.Statements).Any(statement => statement.ScanChildStatements(predicate, skipBlocks)), + IfStatementSyntax ifStatement => ifStatement.Statement.ScanChildStatements(predicate, skipBlocks) + || ifStatement.Else?.Statement.ScanChildStatements(predicate, skipBlocks) == true, + _ => syntax.ChildNodes().Where(n => n is StatementSyntax).Cast() + .Any(s => s.ScanChildStatements(predicate, skipBlocks)), + }; } /// @@ -197,4 +198,41 @@ public static bool ContainsNodeThatVerifies(this SyntaxNode node, Func + /// Cleaned trivia from a node + /// + /// Syntax node exact type + /// node on which to set the trivia + /// a copy with some of the original trivia . + /// uses + public static T WithCleanTrivia(this T node) where T : SyntaxNode + => node.WithCleanTriviaFrom(node); + + /// + /// Inject cleaned up trivia from another syntax node. + /// + /// Syntax node exact type + /// node on which to set the trivia + /// node from which extract the trivia + /// a copy with some trivia from . + /// Current implementation only applies whitespacetrivia (no comment, no attribute nor directives) + public static T WithCleanTriviaFrom(this T node, T triviaSource) where T: SyntaxNode + => node.WithLeadingTrivia(CleanupTrivia(triviaSource.GetLeadingTrivia())) + .WithTrailingTrivia(CleanupTrivia(triviaSource.GetTrailingTrivia())); + + /// + /// Inject cleaned up trivia from another syntax node. + /// + /// token on which to set the trivia + /// node from which extract the trivia + /// a copy with some trivia from . + /// Current implementation only applies whitespacetrivia (no comment, no attribute nor directives) + public static SyntaxToken WithCleanTriviaFrom(this SyntaxToken token, SyntaxToken triviaSource) + => token.WithLeadingTrivia(CleanupTrivia(triviaSource.LeadingTrivia)) + .WithTrailingTrivia(CleanupTrivia(triviaSource.TrailingTrivia)); + + private static IEnumerable CleanupTrivia(SyntaxTriviaList list) + => list.Where(t=>t.IsKind(SyntaxKind.WhitespaceTrivia) || t.IsKind(SyntaxKind.EndOfLineTrivia)); + } diff --git a/src/Stryker.Core/Stryker.Core/Instrumentation/ConditionalInstrumentationEngine.cs b/src/Stryker.Core/Stryker.Core/Instrumentation/ConditionalInstrumentationEngine.cs index e52ee5d5e..f7d6f09c5 100644 --- a/src/Stryker.Core/Stryker.Core/Instrumentation/ConditionalInstrumentationEngine.cs +++ b/src/Stryker.Core/Stryker.Core/Instrumentation/ConditionalInstrumentationEngine.cs @@ -2,6 +2,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Stryker.Core.Helpers; namespace Stryker.Core.Instrumentation; @@ -22,15 +23,16 @@ public ParenthesizedExpressionSyntax PlaceWithConditionalExpression(ExpressionSy SyntaxFactory.ConditionalExpression( condition: condition, whenTrue: mutated, - whenFalse: original)) - // Mark this node as a MutationConditional node. Store the MutantId in the annotation to retrace the mutant later - .WithAdditionalAnnotations(Marker); + whenFalse: original)). + WithTriviaFrom(original). + // Mark this node as a MutationConditional node. Store the MutantId in the annotation to retrace the mutant later + WithAdditionalAnnotations(Marker); protected override SyntaxNode Revert(ParenthesizedExpressionSyntax parenthesized) { if (parenthesized.Expression is ConditionalExpressionSyntax conditional) { - return conditional.WhenFalse; + return conditional.WhenFalse.WithTriviaFrom(parenthesized); } throw new InvalidOperationException($"Expected a block containing a conditional expression, found:\n{parenthesized.ToFullString()}."); } diff --git a/src/Stryker.Core/Stryker.Core/Instrumentation/IfInstrumentationEngine.cs b/src/Stryker.Core/Stryker.Core/Instrumentation/IfInstrumentationEngine.cs index 89d242a7d..992b96ce0 100644 --- a/src/Stryker.Core/Stryker.Core/Instrumentation/IfInstrumentationEngine.cs +++ b/src/Stryker.Core/Stryker.Core/Instrumentation/IfInstrumentationEngine.cs @@ -18,10 +18,12 @@ internal class IfInstrumentationEngine : BaseEngine /// Mutated code /// A statement containing the expected construct. /// This method works with statement and block. - public IfStatementSyntax InjectIf(ExpressionSyntax condition, StatementSyntax originalNode, StatementSyntax mutatedNode) => - SyntaxFactory.IfStatement(condition, + public IfStatementSyntax InjectIf(ExpressionSyntax condition, StatementSyntax originalNode, StatementSyntax mutatedNode) + => SyntaxFactory.IfStatement(condition, AsBlock(mutatedNode), - SyntaxFactory.ElseClause(AsBlock(originalNode))).WithAdditionalAnnotations(Marker); + SyntaxFactory.ElseClause(AsBlock(originalNode.WithoutTrivia()))). + WithTriviaFrom(originalNode). + WithAdditionalAnnotations(Marker); private static BlockSyntax AsBlock(StatementSyntax code) => code as BlockSyntax ?? SyntaxFactory.Block(code); @@ -35,7 +37,7 @@ protected override SyntaxNode Revert(IfStatementSyntax ifNode) { if (ifNode.Else?.Statement is BlockSyntax block) { - return block.Statements.Count == 1 ? block.Statements[0] : block; + return (block.Statements.Count == 1 ? block.Statements[0] : block).WithTriviaFrom(ifNode); } throw new InvalidOperationException($"Expected a block containing an 'else' statement, found:\n{ifNode.ToFullString()}."); } diff --git a/src/Stryker.Core/Stryker.Core/Mutants/CsharpMutantOrchestrator.cs b/src/Stryker.Core/Stryker.Core/Mutants/CsharpMutantOrchestrator.cs index 09b193245..0ea68a8d8 100644 --- a/src/Stryker.Core/Stryker.Core/Mutants/CsharpMutantOrchestrator.cs +++ b/src/Stryker.Core/Stryker.Core/Mutants/CsharpMutantOrchestrator.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; - using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -47,8 +46,6 @@ private static List BuildOrchestratorList() => new DoNotMutateOrchestrator(), // enum values new DoNotMutateOrchestrator(), - // pattern marching - new DoNotMutateOrchestrator(), new DoNotMutateOrchestrator(), // constants and constant fields new DoNotMutateOrchestrator( @@ -65,6 +62,9 @@ private static List BuildOrchestratorList() => new MemberAccessExpressionOrchestrator(), new MemberAccessExpressionOrchestrator(t => t.IsKind(SyntaxKind.SuppressNullableWarningExpression)), + // ensure pattern syntax nodes are mutated (as they are neither expression nor statements, they are not mutated by default) + new NodeSpecificOrchestrator(), + new NodeSpecificOrchestrator(), new ConditionalExpressionOrchestrator(), new ConstantPatternSyntaxOrchestrator(), // ensure static constructs are marked properly @@ -95,6 +95,10 @@ private static List BuildOrchestratorList() => private static List DefaultMutatorList() => [ new BinaryExpressionMutator(), + new RelationalPatternMutator(), + new BinaryPatternMutator(), + new StringMethodMutator(), + new StringMethodToConstantMutator(), new BlockMutator(), new BooleanMutator(), new ConditionalExpressionMutator(), @@ -114,10 +118,8 @@ private static List DefaultMutatorList() => new RegexMutator(), new NullCoalescingExpressionMutator(), new MathMutator(), - new SwitchExpressionMutator(), new IsPatternExpressionMutator(), - new StringMethodMutator(), - new CollectionExpressionMutator() + new CollectionExpressionMutator(), ]; private IEnumerable Mutators { get; } @@ -143,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)) @@ -150,8 +153,8 @@ internal IEnumerable GenerateMutationsForNode(SyntaxNode current, Semant continue; } newMutant.Id = GetNextId(); - Logger.LogDebug("Mutant {MutantId} created {OriginalNode} -> {ReplacementNode} using {Mutator}", newMutant.Id, mutation.OriginalNode, - mutation.ReplacementNode, mutator.GetType()); + Logger.LogDebug("Mutant {MutantId} created {OriginalNode} -> {ReplacementNode} using {Mutator}", + newMutant.Id, mutation.OriginalNode, mutation.ReplacementNode, mutator.GetType()); Mutants.Add(newMutant); mutations.Add(newMutant); } 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 new file mode 100644 index 000000000..e64c40e46 --- /dev/null +++ b/src/Stryker.Core/Stryker.Core/Mutators/BinaryPatternMutator.cs @@ -0,0 +1,43 @@ +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 BinaryPatternMutator : MutatorBase +{ + public override MutationLevel MutationLevel => MutationLevel.Basic; + + private Dictionary> KindsToMutate { get; } = new() + { + [SyntaxKind.OrPattern] = [SyntaxKind.AndPattern], + [SyntaxKind.AndPattern] = [SyntaxKind.OrPattern], + }; + + public override IEnumerable ApplyMutations(BinaryPatternSyntax node, SemanticModel semanticModel) + { + if (!KindsToMutate.TryGetValue(node.Kind(), out var mutations)) + { + yield break; + } + + foreach (var mutation in mutations) + { + // can't use the update method here, because roslyn implementation is broken + var replacementNode = SyntaxFactory.BinaryPattern(mutation, node.Left.WithCleanTrivia(), node.Right.WithCleanTrivia()); + replacementNode = replacementNode.WithOperatorToken(replacementNode.OperatorToken.WithCleanTriviaFrom(node.OperatorToken)); + yield return new() + { + OriginalNode = node, + ReplacementNode = replacementNode, + DisplayName = "Logical mutation", + Type = Mutator.Logical + }; + } + + } +} 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 2d20260f5..1105182a5 100644 --- a/src/Stryker.Core/Stryker.Core/Mutators/IsPatternExpressionMutator.cs +++ b/src/Stryker.Core/Stryker.Core/Mutators/IsPatternExpressionMutator.cs @@ -1,16 +1,18 @@ 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; /// Mutator implementation for is expression -public class IsPatternExpressionMutator : PatternMutatorBase +public class IsPatternExpressionMutator : MutatorBase { + public override MutationLevel MutationLevel => MutationLevel.Basic; + /// /// Apply mutations to all inside an . /// Apply mutations to the root pattern. @@ -18,31 +20,21 @@ public class IsPatternExpressionMutator : PatternMutatorBase ApplyMutations(IsPatternExpressionSyntax node, SemanticModel semanticModel) { yield return ReverseRootPattern(node); - - var descendantMutations = node - .DescendantNodes() - .OfType() - .SelectMany(x => ApplyMutations(x, semanticModel)); - - foreach (var descendantMutation in descendantMutations) - { - yield return descendantMutation; - } } private static Mutation ReverseRootPattern(IsPatternExpressionSyntax node) => node.Pattern switch { UnaryPatternSyntax notPattern => new Mutation { - OriginalNode = notPattern, - ReplacementNode = notPattern.Pattern, + OriginalNode = node, + ReplacementNode = node.WithCleanTrivia().WithPattern(notPattern.WithCleanTrivia().Pattern), Type = Mutator.Equality, DisplayName = "Equality mutation" }, _ => new Mutation { - OriginalNode = node.Pattern, - ReplacementNode = SyntaxFactory.UnaryPattern(node.Pattern.WithLeadingTrivia(SyntaxFactory.Space)), + OriginalNode = node, + 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 afc2d4944..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; @@ -18,25 +19,18 @@ public override IEnumerable ApplyMutations(BinaryExpressionSyntax node yield break; } - // Flip left and right - var replacementNode = SyntaxFactory - .BinaryExpression(SyntaxKind.CoalesceExpression, node.Right, node.Left); - replacementNode = replacementNode.WithOperatorToken(replacementNode.OperatorToken - .WithTriviaFrom(node.OperatorToken) - .WithLeadingTrivia(node.Left.GetTrailingTrivia()) - ); - + var rightPartIsNullable = IsNullable(node.Right, semanticModel); // Do not create "left to right", or "remove right" mutants when the right // hand side is a throw expression, as they result in invalid code. if (!node.Right.IsKind(SyntaxKind.ThrowExpression)) { // Only create a "left to right" mutant if both sides are nullable. - if (IsNullable(node.Right, semanticModel)) + if (rightPartIsNullable) { yield return new Mutation { OriginalNode = node, - ReplacementNode = replacementNode, + ReplacementNode = node.WithLeft(node.Right).WithRight(node.Left).WithCleanTrivia(), DisplayName = "Null coalescing mutation (left to right)", Type = Mutator.NullCoalescing }; @@ -45,23 +39,24 @@ public override IEnumerable ApplyMutations(BinaryExpressionSyntax node yield return new Mutation { OriginalNode = node, - ReplacementNode = replacementNode.Left, - DisplayName = "Null coalescing mutation (remove right)", + ReplacementNode = node.Right.WithCleanTrivia(), + DisplayName = "Null coalescing mutation (remove left)", Type = Mutator.NullCoalescing }; } - // Only create a "remove left" mutant if the right side is nullable. - if (IsNullable(node.Right, semanticModel) || node.Right.IsKind(SyntaxKind.CollectionExpression)) + // Only create a "remove right" mutant if the right side is nullable. + if (rightPartIsNullable || node.Right.IsKind(SyntaxKind.CollectionExpression)) { yield return new Mutation { OriginalNode = node, - ReplacementNode = replacementNode.Right, - DisplayName = $"Null coalescing mutation (remove left)", + ReplacementNode = node.Left.WithCleanTrivia(), + DisplayName = $"Null coalescing mutation (remove right)", Type = Mutator.NullCoalescing }; } + } private static bool IsNullable(SyntaxNode node, SemanticModel semanticModel) @@ -74,6 +69,8 @@ private static bool IsNullable(SyntaxNode node, SemanticModel semanticModel) } var typeInfo = semanticModel.GetTypeInfo(node); - return typeInfo.Nullability.FlowState == NullableFlowState.MaybeNull; + // assume nullability if type resolution failed for some reason + return (typeInfo.ConvertedType is { } && typeInfo.ConvertedType.TypeKind is TypeKind.Error or TypeKind.Unknown) + || typeInfo.Nullability.FlowState == NullableFlowState.MaybeNull; } } 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/PatternMutatorBase.cs b/src/Stryker.Core/Stryker.Core/Mutators/PatternMutatorBase.cs deleted file mode 100644 index f5479d445..000000000 --- a/src/Stryker.Core/Stryker.Core/Mutators/PatternMutatorBase.cs +++ /dev/null @@ -1,76 +0,0 @@ -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; - -namespace Stryker.Core.Mutators; - -/// Base Mutator implementation for expressions with patterns -public abstract class PatternMutatorBase : MutatorBase where T : ExpressionSyntax -{ - public override MutationLevel MutationLevel => MutationLevel.Basic; - - /// Dictionary which maps original syntax kinds to target mutations - /// This could be a static field, but sonar does not like static fields for generic types, and the extra runtime cost is negligible. - private Dictionary> KindsToMutate { get; } = new() - { - [SyntaxKind.OrPattern] = new[] { SyntaxKind.AndPattern }, - [SyntaxKind.AndPattern] = new[] { SyntaxKind.OrPattern }, - [SyntaxKind.LessThanEqualsToken] = new[] { SyntaxKind.GreaterThanToken, SyntaxKind.LessThanToken }, - [SyntaxKind.GreaterThanEqualsToken] = new[] { SyntaxKind.GreaterThanToken, SyntaxKind.LessThanToken }, - [SyntaxKind.LessThanToken] = new[] { SyntaxKind.GreaterThanToken, SyntaxKind.LessThanEqualsToken }, - [SyntaxKind.GreaterThanToken] = new[] { SyntaxKind.LessThanToken, SyntaxKind.GreaterThanEqualsToken } - }; - - /// Apply mutations to a - protected IEnumerable ApplyMutations(PatternSyntax node, SemanticModel semanticModel) => node switch - { - BinaryPatternSyntax binaryPattern => ApplyMutations(binaryPattern), - RelationalPatternSyntax relationalPattern => ApplyMutations(relationalPattern), - _ => Enumerable.Empty() - }; - - private IEnumerable ApplyMutations(BinaryPatternSyntax node) - { - if (!KindsToMutate.TryGetValue(node.Kind(), out var mutations)) - { - yield break; - } - - 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)); - yield return new() - { - OriginalNode = node, - ReplacementNode = replacementNode, - DisplayName = "Logical mutation", - Type = Mutator.Logical - }; - } - } - - private IEnumerable ApplyMutations(RelationalPatternSyntax node) - { - if (!KindsToMutate.TryGetValue(node.OperatorToken.Kind(), out var mutations)) - { - yield break; - } - - foreach (var mutation in mutations) - { - yield return new() - { - OriginalNode = node, - ReplacementNode = node.WithOperatorToken(SyntaxFactory.Token(mutation).WithTriviaFrom(node.OperatorToken)), - DisplayName = "Equality mutation", - Type = Mutator.Equality - }; - } - } -} 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 new file mode 100644 index 000000000..a40960f33 --- /dev/null +++ b/src/Stryker.Core/Stryker.Core/Mutators/RelationalPatternMutator.cs @@ -0,0 +1,41 @@ +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 RelationalPatternMutator : MutatorBase +{ + public override MutationLevel MutationLevel => MutationLevel.Basic; + + private Dictionary> KindsToMutate { get; } = new() + { + [SyntaxKind.LessThanEqualsToken] = [SyntaxKind.GreaterThanToken, SyntaxKind.LessThanToken], + [SyntaxKind.GreaterThanEqualsToken] = [SyntaxKind.GreaterThanToken, SyntaxKind.LessThanToken], + [SyntaxKind.LessThanToken] = [SyntaxKind.GreaterThanToken, SyntaxKind.LessThanEqualsToken], + [SyntaxKind.GreaterThanToken] = [SyntaxKind.LessThanToken, SyntaxKind.GreaterThanEqualsToken] + }; + + public override IEnumerable ApplyMutations(RelationalPatternSyntax node, SemanticModel semanticModel) + { + if (!KindsToMutate.TryGetValue(node.OperatorToken.Kind(), out var mutations)) + { + yield break; + } + + foreach (var mutation in mutations) + { + yield return new() + { + OriginalNode = node, + 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..84bb7f54f 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)) @@ -44,7 +43,7 @@ private IEnumerable ApplyMutations(MemberAccessExpressionSyntax node) } } - private IEnumerable ApplyMutations(InvocationExpressionSyntax node) + private static IEnumerable ApplyMutations(InvocationExpressionSyntax node) { if (node.Expression is MemberAccessExpressionSyntax memberAccessExpression && IsAccessToStringPredefinedType(memberAccessExpression.Expression)) @@ -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 99367a780..5aa68eb00 100644 --- a/src/Stryker.Core/Stryker.Core/Mutators/StringMethodMutator.cs +++ b/src/Stryker.Core/Stryker.Core/Mutators/StringMethodMutator.cs @@ -1,53 +1,30 @@ 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; /// Mutator Implementation for String method Mutations -public class StringMethodMutator : MutatorBase +public class StringMethodMutator : MutatorBase { public override MutationLevel MutationLevel => MutationLevel.Advanced; - public override IEnumerable ApplyMutations( - ExpressionSyntax node, - SemanticModel semanticModel - ) => - node switch - { - InvocationExpressionSyntax invocation => ApplyInvocationMutations(invocation, semanticModel), - _ => Enumerable.Empty() - }; - - private IEnumerable ApplyInvocationMutations(InvocationExpressionSyntax node, SemanticModel model) + public override IEnumerable ApplyMutations(MemberAccessExpressionSyntax member, SemanticModel semanticModel) { - if (node is not { Expression: MemberAccessExpressionSyntax member }) - yield break; - - if (!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: - var replacement = GetReplacement(identifier); - if (replacement == null) - yield break; + var replacement = GetReplacement(identifier); - yield return ApplyReplaceMutation(member, identifier, replacement); - break; + if (replacement == null || (semanticModel!= null && !member.Expression.IsAStringExpression(semanticModel))) + { + yield break; } + + yield return ApplyReplaceMutation(member, identifier, replacement); } private static string GetReplacement(string identifier) => @@ -68,66 +45,12 @@ 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 }; - - 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 - }; } 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..2e0639dcc --- /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 semanticModel) + { + if (node is not { Expression: MemberAccessExpressionSyntax member } + || (semanticModel != null && !member.Expression.IsAStringExpression(semanticModel))) + { + 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)), diff --git a/src/Stryker.Core/Stryker.Core/Mutators/SwitchExpressionMutator.cs b/src/Stryker.Core/Stryker.Core/Mutators/SwitchExpressionMutator.cs deleted file mode 100644 index 904ac8338..000000000 --- a/src/Stryker.Core/Stryker.Core/Mutators/SwitchExpressionMutator.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Stryker.Abstractions.Mutants; - -namespace Stryker.Core.Mutators; - -/// Mutator implementation for switch expression -public class SwitchExpressionMutator : PatternMutatorBase -{ - /// Apply mutations to all inside an - public override IEnumerable ApplyMutations(SwitchExpressionSyntax node, SemanticModel semanticModel) => node - .DescendantNodes() - .OfType() - .SelectMany(x => ApplyMutations(x, semanticModel)); -}