diff --git a/packages/api/schema/stryker-core.json b/packages/api/schema/stryker-core.json index ba767d4807..cc210a4617 100644 --- a/packages/api/schema/stryker-core.json +++ b/packages/api/schema/stryker-core.json @@ -220,16 +220,18 @@ }, "includedMutations": { "type": "array", + "uniqueItems": true, "default": [], "items": { - "$ref": "#/definitions/MutatorDefinition" + "$ref": "#/definitions/MutationSpecification" } }, "excludedMutations": { "type": "array", + "uniqueItems": true, "default": [], "items": { - "$ref": "#/definitions/MutatorDefinition" + "$ref": "#/definitions/MutationSpecification" } } } @@ -261,25 +263,35 @@ } } }, + "MutationSpecification": { + "anyOf": [ + { "$ref": "#/definitions/MutationLevelName"}, + { "$ref": "#/definitions/MutatorDefinition"}, + { "$ref": "#/definitions/MutatorGroupName"} + ] + }, + "MutationLevelName": { + "title": "MutationLevelName", + "type": "string" + }, "MutatorDefinition" : { "anyOf": [ { "$ref": "#/definitions/ArithmeticOperator" }, { "$ref": "#/definitions/ArrayDeclaration" }, + { "$ref": "#/definitions/ArrowFunction"}, { "$ref": "#/definitions/AssignmentOperator" }, { "$ref": "#/definitions/BlockStatement" }, - { "$ref": "#/definitions/ArrowFunction"}, { "$ref": "#/definitions/BooleanLiteral" }, { "$ref": "#/definitions/ConditionalExpression" }, { "$ref": "#/definitions/EqualityOperator" }, + { "$ref": "#/definitions/LogicalOperator"}, { "$ref": "#/definitions/MethodExpression" }, { "$ref": "#/definitions/ObjectLiteral" }, { "$ref": "#/definitions/OptionalChaining" }, { "$ref": "#/definitions/Regex" }, { "$ref": "#/definitions/StringLiteral" }, { "$ref": "#/definitions/UnaryOperator" }, - { "$ref": "#/definitions/UpdateOperator" }, - { "$ref": "#/definitions/MutatorGroupName"}, - { "title": "MutationLevelName", "type": "string"} + { "$ref": "#/definitions/UpdateOperator" } ] }, "MutatorGroupName": { @@ -302,28 +314,23 @@ "title": "ArithmeticOperator", "anyOf": [ { - "const" : "%To*", - "title": "PercentToMultiplyMutator", - "description": "Replace ```a % b``` with ```a * b```." + "const" : "AdditionOperatorNegation", + "description": "Replace ```a + b``` with ```a - b```." }, { - "const" : "*To/", - "title": "MultiplyToDivideMutator", - "description": "Replace ```a * b``` with ```a / b```." + "const" : "DivisionOperatorNegation", + "description": "Replace ```a / b``` with ```a * b```." }, { - "const" : "/To*", - "title": "DivideToMultiplyMutator", - "description": "Replace ```a / b``` with ```a * b```." + "const" : "MultiplicationOperatorNegation", + "description": "Replace ```a * b``` with ```a / b```." }, { - "const" : "+To-", - "title": "PlusToMinusMutator", - "description": "Replace ```a + b``` with ```a - b```." + "const" : "RemainderOperatorToMultiplicationReplacement", + "description": "Replace ```a % b``` with ```a * b```." }, { - "const" : "-To+", - "title": "MinusToPlusMutator", + "const" : "SubtractionOperatorNegation", "description": "Replace ```a - b``` with ```a + b```." } ] @@ -332,24 +339,20 @@ "title": "ArrayDeclaration", "anyOf": [ { - "const": "EmptyArray", - "title": "EmptyArrayMutator", - "description": "Replace ```[ ]``` with ```[Stryker was here]```." + "const": "ArrayConstructorItemsFill", + "description": "Replace ```new Array()``` with ```new Array(Stryker was here)```." }, { - "const": "EmptyArrayConstructor", - "title": "EmptyConstructorMutator", - "description": "Replace ```new Array()``` with ```new Array(Stryker was here)```." + "const": "ArrayConstructorItemsRemoval", + "description": "Replace ```new Array([1, 2, 3, 4])``` with ```new Array()```." }, { - "const": "FilledArray", - "title": "FilledArrayMutator", - "description": "Replace ```[1, 2, 3, 4]``` with ```[ ]```." + "const": "ArrayLiteralItemsFill", + "description": "Replace ```[ ]``` with ```[Stryker was here]```." }, { - "const": "FilledArrayConstructor", - "title": "FilledArrayConstructorMutator", - "description": "Replace ```new Array([1, 2, 3, 4])``` with ```new Array()```." + "const": "ArrayLiteralItemsRemoval", + "description": "Replace ```[1, 2, 3, 4]``` with ```[ ]```." } ] }, @@ -357,92 +360,77 @@ "title": "AssignmentOperator", "anyOf": [ { - "const" : "+=To-=", - "title": "PlusAssignmentToMinusAssignmentMutator", + "const" : "AdditionAssignmentNegation", "description": "Replace ```a += b``` with ```a -= b```." }, { - "const" : "-=To+=", - "title": "MinusAssignmentToPlusAssignmentMutator", - "description": "Replace ```a -= b``` with ```a += b```." + "const" : "BitwiseAndAssignmentNegation", + "description": "Replace ```a &= b``` with ```a |= b```." }, { - "const" : "*=To/=", - "title": "MultiplyAssignmentToDivideAssignmentMutator", - "description": "Replace ```a *= b``` with ```a /= b```." + "const" : "BitwiseOrAssignmentNegation", + "description": "Replace ```a |= b``` with ```a &= b```." }, { - "const" : "/=To*=", - "title": "DivideAssignmentToMultiplyAssignmentMutator", + "const" : "DivisionAssignmentNegation", "description": "Replace ```a /= b``` with ```a *= b```." }, { - "const" : "%=To*=", - "title": "ModuloAssignmentToMultiplyAssignmentMutator", - "description": "Replace ```a %= b``` with ```a *= b```." + "const" : "LeftShiftAssignmentNegation", + "description": "Replace ```a <<= b``` with ```a >>= b```." }, { - "const" : "<<=To>>=", - "title": "LeftShiftAssignmentToRightShiftAssignmentMutator", - "description": "Replace ```a <<= b``` with ```a >>= b```." + "const" : "LogicalAndAssignmentNegation", + "description": "Replace ```a &&= b``` with ```a ||= b```." }, { - "const" : ">>=To<<=", - "title": "RightShiftAssignmentToLeftShiftAssignmentMutator", - "description": "Replace ```a >>= b``` with ```a <<= b```." + "const" : "LogicalOrAssignmentNegation", + "description": "Replace ```a ||= b``` with ```a &&= b```." }, { - "const" : "&=To|=", - "title": "BitAndAssignmentToBitOrAssignmentMutator", - "description": "Replace ```a &= b``` with ```a |= b```." + "const" : "MultiplicationAssignmentNegation", + "description": "Replace ```a *= b``` with ```a /= b```." }, { - "const" : "|=To&=", - "title": "BitOrAssignmentToBitAndAssignmentMutator", - "description": "Replace ```a |= b``` with ```a &= b```." + "const" : "NullishCoalescingAssignmentToLogicalAndReplacement", + "description": "Replace ```a ??= b``` with ```a &&= b```." }, { - "const" : "&&=To||=", - "title": "LogicalAndAssignmentToLogicalOrAssignmentMutator", - "description": "Replace ```a &&= b``` with ```a ||= b```." + "const" : "RemainderAssignmentToMultiplicationReplacement", + "description": "Replace ```a %= b``` with ```a *= b```." }, { - "const" : "||=To&&=", - "title": "LogicalOrAssignmentToLogicalAndAssignmentMutator", - "description": "Replace ```a ||= b``` with ```a &&= b```." + "const" : "RightShiftAssignmentNegation", + "description": "Replace ```a >>= b``` with ```a <<= b```." }, { - "const" : "??=To&&=", - "title": "NullishCoalescingAssignmentToLogicalAndAssignmentMutator", - "description": "Replace ```a ??= b``` with ```a &&= b```." + "const" : "SubtractionAssignmentNegation", + "description": "Replace ```a -= b``` with ```a += b```." } ] }, "ArrowFunction": { - "const": "ArrowFunction", + "const": "ArrowFunctionRemoval", "description": "Mutates bodies of arrow functions to undefined" }, "BlockStatement": { - "const": "BlockStatement", + "const": "BlockStatementRemoval", "description": "Removes the content of every block statement." }, "BooleanLiteral": { "title": "BooleanLiteral", "anyOf": [ { - "const" : "FalseToTrue", - "title": "FalseToTrueMutator", - "description": "Replace ```true``` with ```false```." + "const" : "FalseLiteralNegation", + "description": "Replace ```false``` with ```true```." }, { - "const" : "TrueToFalse", - "title": "TrueToFalseMutator", - "description": "Replace ```true``` with ```false```." + "const" : "LogicalNotRemoval", + "description": "Replace ```!(a == b)``` with ```a == b```." }, { - "const" : "RemoveNegation", - "title": "RemoveNegationMutator", - "description": "Replace ```!(a == b)``` with ```a == b```." + "const" : "TrueLiteralNegation", + "description": "Replace ```true``` with ```false```." } ] }, @@ -450,109 +438,89 @@ "title": "ConditionalExpression", "anyOf": [ { - "const" : "ForLoopToFalse", - "title": "ForLoopToFalseMutator", - "description": "Replace ```for (var i = 0; i < 10; i++) { }``` with ```for (var i = 0; false; i++) { }```." + "const" : "BooleanExpressionToFalseReplacement", + "description": "Replace ```var x = a > b ? 1 : 2;``` with ```var x = false ? 1 : 2;```." }, { - "const" : "WhileLoopToFalse", - "title": "WhileLoopToFalseMutator", - "description": "Replace ```while (a > b) { }``` with ```while (false) { }```." + "const" : "BooleanExpressionToTrueReplacement", + "description": "Replace ```var x = a > b ? 1 : 2;``` with ```var x = true ? 1 : 2;```." }, { - "const" : "DoWhileLoopToFalse", - "title": "DoWhileLoopToFalseMutator", + "const" : "DoWhileLoopConditionToFalseReplacement", "description": "Replace ```do { } while (a > b);``` with ```do { } while (false);```." }, { - "const" : "IfToTrue", - "title": "IfToTrueMutator", - "description": "Replace ```if (a > b) { }``` with ```if (true) { }```." + "const" : "ForLoopConditionToFalseReplacement", + "description": "Replace ```for (var i = 0; i < 10; i++) { }``` with ```for (var i = 0; false; i++) { }```." }, { - "const" : "IfToFalse", - "title": "IfToFalseMutator", + "const" : "IfConditionToFalseReplacement", "description": "Replace ```if (a > b) { }``` with ```if (false) { }```." }, { - "const" : "BooleanExpressionToTrue", - "title": "BooleanExpressionToTrueMutator", - "description": "Replace ```var x = a > b ? 1 : 2;``` with ```var x = true ? 1 : 2;```." + "const" : "IfConditionToTrueReplacement", + "description": "Replace ```if (a > b) { }``` with ```if (true) { }```." }, { - "const" : "BooleanExpressionToFalse", - "title": "BooleanExpressionToFalseMutator", - "description": "Replace ```var x = a > b ? 1 : 2;``` with ```var x = false ? 1 : 2;```." + "const" : "SwitchStatementBodyRemoval", + "description": "Replace ```switch(x) with switch()```." }, { - "const" : "SwitchToEmpty", - "title": "SwitchToEmptyMutator", - "description": "Replace ```switch(x) with switch()```." + "const" : "WhileLoopConditionToFalseReplacement", + "description": "Replace ```while (a > b) { }``` with ```while (false) { }```." } ] }, - "EqualityOperator": { + "EqualityOperator": { "title": "EqualityOperator", "anyOf": [ { - "const" : "!==To===", - "title": "StrictDiffersToStrictEqualsMutator", - "description": "Replace ```a !== b``` with ```a === b```." + "const" : "EqualityOperatorNegation", + "description": "Replace ```a == b``` with ```a != b```." }, { - "const" : "!=To==", - "title": "DifferentToEqualsMutator", - "description": "Replace ```a != b``` with ```a == b```." + "const" : "GreaterThanEqualOperatorBoundary", + "description": "Replace ```a >= b``` with ```a > b```." }, { - "const" : "<=To<", - "title": "SmallerOrEqualToSmallerMutator", - "description": "Replace ```a <= b``` with ```a < b```." + "const" : "GreaterThanEqualOperatorNegation", + "description": "Replace ```a >= b``` with ```a < b```." }, { - "const" : "<=To>", - "title": "SmallerOrEqualToBiggerMutator", - "description": "Replace ```a <= b``` with ```a > b```." + "const" : "GreaterThanOperatorBoundary", + "description": "Replace ```a > b``` with ```a >= b```." }, { - "const" : " b``` with ```a <= b```." }, { - "const" : "=", - "title": "SmallerToBiggerOrEqualMutator", - "description": "Replace ```a < b``` with ```a >= b```." + "const" : "InequalityOperatorNegation", + "description": "Replace ```a != b``` with ```a == b```." }, { - "const" : "===To!==", - "title": "StrictEqualsToStrictDiffersMutator", - "description": "Replace ```a === b``` with ```a !== b```." + "const" : "LessThanEqualOperatorBoundary", + "description": "Replace ```a <= b``` with ```a < b```." }, { - "const" : "==To!=", - "title": "EqualsToDiffersMutator", - "description": "Replace ```a == b``` with ```a != b```." + "const" : "LessThanEqualOperatorNegation", + "description": "Replace ```a <= b``` with ```a > b```." }, { - "const" : ">=To<", - "title": "BiggerOrEqualToSmallerMutator", - "description": "Replace ```a >= b``` with ```a < b```." + "const" : "LessThanOperatorBoundary", + "description": "Replace ```a < b``` with ```a <= b```." }, { - "const" : ">=To>", - "title": "BiggerOrEqualToBiggerMutator", - "description": "Replace ```a >= b``` with ```a > b```." + "const" : "LessThanOperatorNegation", + "description": "Replace ```a < b``` with ```a >= b```." }, { - "const" : ">To<=", - "title": "BiggerToSmallerOrEqualMutator", - "description": "Replace ```a > b``` with ```a <= b```." + "const" : "StrictEqualityOperatorNegation", + "description": "Replace ```a === b``` with ```a !== b```." }, { - "const" : ">To>=", - "title": "BiggerToBiggerOrEqualMutator", - "description": "Replace ```a > b``` with ```a >= b```." + "const" : "StrictInequalityOperatorNegation", + "description": "Replace ```a !== b``` with ```a === b```." } ] }, @@ -560,18 +528,15 @@ "title": "LogicalOperator", "anyOf": [ { - "const" : "&&To||", - "title": "AndToOrMutator", + "const" : "LogicalAndOperatorNegation", "description": "Replace ```a && b``` with ```a || b```." }, { - "const" : "||To&&", - "title": "OrToAndMutator", + "const" : "LogicalOrOperatorNegation", "description": "Replace ```a || b``` with ```a && b```." }, { - "const" : "??To&&", - "title": "CoalescingToAndMutator", + "const" : "NullishCoalescingOperatorToLogicalAndReplacement", "description": "Replace ```a ?? b``` with ```a && b```." } ] @@ -580,151 +545,129 @@ "title": "MethodExpression", "anyOf": [ { - "const": "removeCharAt", - "title": "removeCharAtMutator", + "const": "CharAtMethodCallRemoval", "description": "Remove ```charAt()``` call." }, { - "const": "endsWithToStartsWith", - "title": "endsWithToStartsWithMutator", + "const": "EndsWithMethodCallNegation", "description": "Replace ```endsWith()``` with ```startsWith()```." }, { - "const": "startsWithToEndsWith", - "title": "startsWithToEndsWithMutator", - "description": "Replace ```endsWith()``` with ```startsWith()```." + "const": "EveryMethodCallNegation", + "description": "Replace ```every()``` with ```some()```." }, { - "const": "everyToSome", - "title": "everyToSomeMutator", - "description": "Replace ```every()``` with ```some()```." + "const": "FilterMethodCallRemoval", + "description": "Remove ```filter()``` call." }, { - "const": "someToEvery", - "title": "someToEveryMutator", - "description": "Replace ```every()``` with ```some()```." + "const": "MaxMethodCallNegation", + "description": "Replace ```max()``` with ```min()```." }, { - "const": "removeFilter", - "title": "removeFilterMutator", - "description": "Remove ```filter()``` call." + "const": "MinMethodCallNegation", + "description": "Replace ```min()``` with ```max()```." }, { - "const": "removeReverse", - "title": "removeReverseMutator", + "const": "ReverseMethodCallRemoval", "description": "Remove ```reverse()``` call" }, { - "const": "removeSlice", - "title": "removeSliceMutator", + "const": "SliceMethodCallRemoval", "description": "Remove ```slice()``` call." }, { - "const": "removeSort", - "title": "removeSortMutator", - "description": "Remove ```sort()``` call." + "const": "SomeMethodCallNegation", + "description": "Replace ```some()``` with ```every()```." }, { - "const": "removeSubstr", - "title": "removeSubstrMutator", - "description": "Remove ```substr()``` call." + "const": "SortMethodCallRemoval", + "description": "Remove ```sort()``` call." }, { - "const": "removeSubstring", - "title": "removeSubstringMutator", - "description": "Remove ```substring()``` call." + "const": "StartsWithMethodCallNegation", + "description": "Replace ```startsWith()``` with ```endsWith()```." }, { - "const": "toLocaleLowerCaseTotoLocaleUpperCase", - "title": "toLocaleLowerCaseTotoLocaleUpperCaseMutator", - "description": "Replace ```toLocalLowerCase()``` with ```toLocalUpperCase()```." + "const": "SubstringMethodCallRemoval", + "description": "Remove ```substring()``` call." }, { - "const": "toLocaleUpperCaseToToLocaleLowerCase", - "title": "toLocaleUpperCaseToToLocaleLowerCaseMutator", - "description": "Replace ```toLocalLowerCase()``` with ```toLocalUpperCase()```." + "const": "SubstrMethodCallRemoval", + "description": "Remove ```substr()``` call." }, { - "const": "toLowerCaseTotoUpperCase", - "title": "toLowerCaseTotoUpperCaseMutator", - "description": "Replace ```toLocalLowerCase()``` with ```toLocalUpperCase()```." + "const": "ToLocaleLowerCaseMethodCallNegation", + "description": "Replace ```toLocaleLowerCase()``` with ```toLocaleUpperCase()```." }, { - "const": "toUpperCaseToToLowerCase", - "title": "toUpperCaseToToLowerCaseMutator", - "description": "Replace ```toLocalLowerCase()``` with ```toLocalUpperCase()```." + "const": "ToLocaleUpperCaseMethodCallNegation", + "description": "Replace ```toLocaleUpperCase()``` with ```toLocaleLowerCase()```." }, { - "const": "removeTrim", - "title": "removeTrimMutator", - "description": "Remove ```trim()``` call." + "const": "ToLowerCaseMethodCallNegation", + "description": "Replace ```toLowerCase()``` with ```toUpperCase()```." }, { - "const": "trimEndTotrimStart", - "title": "trimEndTotrimStartMutator", - "description": "Replace ```trimEnd()``` with ```trimStart()```." + "const": "ToUpperCaseMethodCallNegation", + "description": "Replace ```toUpperCase()``` with ```toLowerCase()```." }, { - "const": "trimStartToTrimEnd", - "title": "trimStartToTrimEndMutator", + "const": "TrimEndMethodCallNegation", "description": "Replace ```trimEnd()``` with ```trimStart()```." }, { - "const": "minToMax", - "title": "minToMaxMutator", - "description": "Replace ```min()``` with ```max()```." + "const": "TrimMethodCallRemoval", + "description": "Remove ```trim()``` call." }, { - "const": "maxToMin", - "title": "maxToMinMutator", - "description": "Replace ```min()``` with ```max()```." + "const": "TrimStartMethodCallNegation", + "description": "Replace ```trimStart()``` with ```trimEnd()```." } ] }, "ObjectLiteral": { - "const": "ObjectLiteral", + "const": "ObjectLiteralPropertiesRemoval", "description": "Replace ```{ foo: 'bar' }``` with ```{ }```." }, "OptionalChaining": { "title": "OptionalChaining", "anyOf": [ { - "const": "OptionalMemberExpression", - "title": "OptionalMemberExpressionMutator", - "description": "Replace ```foo?.bar``` with ```foo.bar```." + "const": "OptionalCallExpressionOptionalRemoval", + "description": "Replace ```foo?.()``` with ```foo()```." }, { - "const": "OptionalCallExpression", - "title": "OptionalCallExpressionMutator", - "description": "Replace ```foo?.()``` with ```foo()```." + "const": "OptionalComputedMemberExpressionOptionalRemoval", + "description": "Replace ```foo?.[1]``` with ```foo[1]```." + }, + { + "const": "OptionalMemberExpressionOptionalRemoval", + "description": "Replace ```foo?.bar``` with ```foo.bar```." } ] }, "Regex": { - "const": "Regex" + "const": "RegexRemoval" }, "StringLiteral": { "title": "StringLiteral", "anyOf": [ { - "const": "EmptyString", - "title": "EmptyStringMutator", - "description": "Replace ```\"foo\"``` with ```\"\"```." + "const": "EmptyInterpolatedStringToFilledReplacement", + "description": "Replace ```s\"\"``` with ```s\"Stryker was here!\"```." }, { - "const": "FillString", - "title": "FillStringMutator", + "const": "EmptyStringLiteralToFilledReplacement", "description": "Replace ```\"\"``` with ```\"Stryker was here!\"```." }, { - "const": "EmptyInterpolation", - "title": "EmptyInterpolation", + "const": "FilledInterpolatedStringToEmptyReplacement", "description": "Replace ```s\"foo ${bar}\"``` with ```s\"\"```." }, { - "const": "FillInterpolation", - "title": "FillInterpolation", - "description": "Replace ```s\"\"``` with ```s\"Stryker was here!\"```." + "const": "FilledStringLiteralToEmptyReplacement", + "description": "Replace ```\"foo\"``` with ```\"\"```." } ] }, @@ -732,19 +675,16 @@ "title": "UnaryOperator", "anyOf": [ { - "const": "+To-", - "title": "UnaryPlusToUnaryMinusMutator", - "description": "Replace ```+a``` with ```-a```." + "const": "UnaryBitwiseOrRemoval", + "description": "Remove ```~``` from ```~a```." }, { - "const": "-To+", - "title": "UnaryMinusToUnaryPlusMutator", + "const": "UnaryMinOperatorNegation", "description": "Replace ```-a``` with ```+a.```" }, { - "const": "Remove~", - "title": "RemoveTildeMutator", - "description": "" + "const": "UnaryPlusOperatorNegation", + "description": "Replace ```+a``` with ```-a```." } ] }, @@ -752,24 +692,20 @@ "title": "UpdateOperator", "anyOf": [ { - "const": "Post++To--", - "title": "PostPlusToMinusMutator", - "description": "Replace ```a++``` with ```a--```." - }, - { - "const": "Post--To++", - "title": "PostMinusToPlusMutator", + "const": "PostfixDecrementOperatorNegation", "description": "Replace ```a--``` with ```a++```." }, { - "const": "Pre++To--", - "title": "PrePlusToMinusMutator", - "description": "Replace ```++a``` with ```--a```." + "const": "PostfixIncrementOperatorNegation", + "description": "Replace ```a++``` with ```a--```." }, - { - "const": "Pre--To++", - "title": "PreMinusToPlusMutator", + { + "const": "PrefixDecrementOperatorNegation", "description": "Replace ```--a``` with ```++a```." + }, + { + "const": "PrefixIncrementOperatorNegation", + "description": "Replace ```++a``` with ```--a```." } ] } diff --git a/packages/instrumenter/src/mutation-level/default-mutation-levels.json b/packages/instrumenter/src/mutation-level/default-mutation-levels.json index a4b9f743a0..5bb94fb0cd 100644 --- a/packages/instrumenter/src/mutation-level/default-mutation-levels.json +++ b/packages/instrumenter/src/mutation-level/default-mutation-levels.json @@ -1,44 +1,152 @@ { - "mutationLevels": [ - { - "name": "level1", - "UpdateOperator": ["Post--To++"], - "EqualityOperator": ["<=To>","<=To<", "==To!=", "!=To==",">=To<"], - "ArrayDeclaration": ["EmptyArrayConstructor"], - "ConditionalExpression": ["BooleanExpressionToFalse", "BooleanExpressionToTrue"], - "UnaryOperator": ["+To-"], - "AssignmentOperator": ["??=To&&="], - "ArithmeticOperator": ["/To*","%To*","*To/"], - "OptionalChaining": ["OptionalCallExpression","OptionalMemberExpression"] + "mutationLevels":[ + { + "name":"Level1", + "UpdateOperator":[ + "UpdateOperator_PostfixDecrementOperator_ToPostfixIncrementOperator" + ], + "EqualityOperator":[ + "EqualityOperator_LessThanEqualOperator_ToGreatherThanOperator", + "EqualityOperator_LessThanEqualOperator_Boundary", + "EqualityOperator_EqualityOperator_ToInequalityOperator", + "EqualityOperator_InequalityOperator_ToEqualityOperator", + "EqualityOperator_GreatherThanEqualOperator_ToLessThanOperator" + ], + "ArrayDeclaration":[ + "ArrayDeclaration_ArrayConstructor_ItemsRemoval" + ], + "ConditionalExpression":[ + "ConditionalExpression_BooleanExpression_ToFalseLiteral", + "ConditionalExpression_BooleanExpression_ToTrueLiteral" + ], + "UnaryOperator":[ + "UnaryOperator_UnaryPlusOperator_ToUnaryMinusOperator" + ], + "AssignmentOperator":[ + "AssignmentOperator_NullishCoalescingAssignment_ToLogicalAndAssignment" + ], + "ArithmeticOperator":[ + "ArithmeticOperator_DivisionOperator_ToMultiplicationOperator", + "ArithmeticOperator_RemainderOperator_ToMultiplicationOperator", + "ArithmeticOperator_MultiplicationOperator_ToDivisionOperator" + ], + "OptionalChaining":[ + "OptionalChaining_OptionalCallExpression_OptionRemoval", + "OptionalChaining_OptionalMemberExpression_OptionRemoval" + ] }, { - "name": "level2", - "UpdateOperator": ["Post--To++", "Post++To--"], - "EqualityOperator": ["<=To>","<=To<", "==To!=", "!=To==",">=To<","=",">=To>", "!==To===", ">To>="], - "ArrayDeclaration": ["EmptyArrayConstructor"], - "ConditionalExpression": ["BooleanExpressionToFalse", "BooleanExpressionToTrue","SwitchToEmpty" ], - "UnaryOperator": ["+To-"], - "AssignmentOperator": ["??=To&&="], - "ArithmeticOperator": ["/To*","%To*","*To/", "+To-","-To+"], - "OptionalChaining": ["OptionalCallExpression","OptionalMemberExpression"], - "StringLiteral": ["FillString", "FillInterpolation"], - "Regex": true, - "BooleanLiteral": ["TrueToFalse"] + "name":"Level2", + "UpdateOperator":[ + "UpdateOperator_PostfixDecrementOperator_ToPostfixIncrementOperator", + "UpdateOperator_PostfixIncrementOperator_ToPostfixDecrementOperator" + ], + "EqualityOperator":[ + "EqualityOperator_LessThanEqualOperator_ToGreatherThanOperator", + "EqualityOperator_LessThanEqualOperator_Boundary", + "EqualityOperator_EqualityOperator_ToInequalityOperator", + "EqualityOperator_InequalityOperator_ToEqualityOperator", + "EqualityOperator_GreatherThanEqualOperator_ToLessThanOperator", + "EqualityOperator_LessThanOperator_ToGreatherThanEqualOperator", + "EqualityOperator_GreatherThanEqualOperator_Boundary", + "EqualityOperator_StrictInequalityOperator_ToStrictEqualityOperator", + "EqualityOperator_GreaterThanOperator_Boundary" + ], + "ArrayDeclaration":[ + "ArrayDeclaration_ArrayConstructor_ItemsRemoval" + ], + "ConditionalExpression":[ + "ConditionalExpression_BooleanExpression_ToFalseLiteral", + "ConditionalExpression_BooleanExpression_ToTrueLiteral", + "ConditionalExpression_SwitchStatementBody_Removal" + ], + "UnaryOperator":[ + "UnaryOperator_UnaryPlusOperator_ToUnaryMinusOperator" + ], + "AssignmentOperator":[ + "AssignmentOperator_NullishCoalescingAssignment_ToLogicalAndAssignment" + ], + "ArithmeticOperator":[ + "ArithmeticOperator_DivisionOperator_ToMultiplicationOperator", + "ArithmeticOperator_RemainderOperator_ToMultiplicationOperator", + "ArithmeticOperator_MultiplicationOperator_ToDivisionOperator", + "ArithmeticOperator_AdditionOperator_ToSubtractionOperator", + "ArithmeticOperator_SubtractionOperator_ToAdditionOperator" + ], + "OptionalChaining":[ + "OptionalChaining_OptionalCallExpression_OptionRemoval", + "OptionalChaining_OptionalMemberExpression_OptionRemoval" + ], + "StringLiteral":[ + "StringLiteral_EmptyStringLiteral_ToFilledStringLiteral", + "StringLiteral_EmptyInterpolatedString_ToFilledInterpolatedString" + ], + "Regex":[ + "Regex_Removal" + ], + "BooleanLiteral":[ + "BooleanLiteral_TrueLiteral_ToFalseLiteral" + ] }, { - "name": "level3", - "UpdateOperator": ["Post--To++", "Post++To--"], - "EqualityOperator": ["<=To>","<=To<", "==To!=", "!=To==",">=To<","=",">=To>", "!==To===", ">To>=", "To<="], - "ArrayDeclaration": ["EmptyArrayConstructor","EmptyArray", "FilledArray"], - "ConditionalExpression": ["BooleanExpressionToFalse", "BooleanExpressionToTrue","SwitchToEmpty" ], - "UnaryOperator": ["+To-", "-To+"], - "AssignmentOperator": ["??=To&&="], - "ArithmeticOperator": ["/To*","%To*","*To/", "+To-","-To+"], - "OptionalChaining": ["OptionalCallExpression","OptionalMemberExpression"], - "StringLiteral": ["FillString", "FillInterpolation"], - "Regex": true, - "BooleanLiteral": ["TrueToFalse", "FalseToTrue", "RemoveNegation"] + "name":"Level3", + "UpdateOperator":[ + "UpdateOperator_PostfixDecrementOperator_ToPostfixIncrementOperator", + "UpdateOperator_PostfixIncrementOperator_ToPostfixDecrementOperator" + ], + "EqualityOperator":[ + "EqualityOperator_LessThanEqualOperator_ToGreatherThanOperator", + "EqualityOperator_LessThanEqualOperator_Boundary", + "EqualityOperator_EqualityOperator_ToInequalityOperator", + "EqualityOperator_InequalityOperator_ToEqualityOperator", + "EqualityOperator_GreatherThanEqualOperator_ToLessThanOperator", + "EqualityOperator_LessThanOperator_ToGreatherThanEqualOperator", + "EqualityOperator_GreatherThanEqualOperator_Boundary", + "EqualityOperator_StrictInequalityOperator_ToStrictEqualityOperator", + "EqualityOperator_GreaterThanOperator_Boundary", + "EqualityOperator_LessThanOperator_Boundary", + "EqualityOperator_GreaterThanOperator_ToLessThanEqualOperator" + ], + "ArrayDeclaration":[ + "ArrayDeclaration_ArrayConstructor_ItemsRemoval", + "EmptyArray", + "FilledArray" + ], + "ConditionalExpression":[ + "ConditionalExpression_BooleanExpression_ToFalseLiteral", + "ConditionalExpression_BooleanExpression_ToTrueLiteral", + "ConditionalExpression_SwitchStatementBody_Removal" + ], + "UnaryOperator":[ + "UnaryOperator_UnaryPlusOperator_ToUnaryMinusOperator", + "ArithmeticOperator_SubtractionOperator_ToAdditionOperator" + ], + "AssignmentOperator":[ + "AssignmentOperator_NullishCoalescingAssignment_ToLogicalAndAssignment" + ], + "ArithmeticOperator":[ + "ArithmeticOperator_DivisionOperator_ToMultiplicationOperator", + "ArithmeticOperator_RemainderOperator_ToMultiplicationOperator", + "ArithmeticOperator_MultiplicationOperator_ToDivisionOperator", + "ArithmeticOperator_AdditionOperator_ToSubtractionOperator", + "ArithmeticOperator_SubtractionOperator_ToAdditionOperator" + ], + "OptionalChaining":[ + "OptionalChaining_OptionalCallExpression_OptionRemoval", + "OptionalChaining_OptionalMemberExpression_OptionRemoval" + ], + "StringLiteral":[ + "StringLiteral_EmptyStringLiteral_ToFilledStringLiteral", + "StringLiteral_EmptyInterpolatedString_ToFilledInterpolatedString" + ], + "Regex":[ + "Regex_Removal" + ], + "BooleanLiteral":[ + "BooleanLiteral_TrueLiteral_ToFalseLiteral", + "BooleanLiteral_FalseLiteral_ToTrueLiteral", + "BooleanLiteral_LogicalNot_Removal" + ] } - ] - } - \ No newline at end of file + ] +} diff --git a/packages/instrumenter/src/mutation-level/mutation-level.ts b/packages/instrumenter/src/mutation-level/mutation-level.ts index 24aa991c21..b9d6f6a156 100644 --- a/packages/instrumenter/src/mutation-level/mutation-level.ts +++ b/packages/instrumenter/src/mutation-level/mutation-level.ts @@ -9,6 +9,7 @@ import { BooleanLiteral, ConditionalExpression, EqualityOperator, + LogicalOperator, MethodExpression, ObjectLiteral, OptionalChaining, @@ -18,12 +19,11 @@ import { UpdateOperator, } from '@stryker-mutator/api/core'; -export type NodeMutatorConfiguration = Record; +export type NodeMutatorConfiguration = Record>; -export type NodeMutatorMultiConfiguration = Record; -interface ReplacementConfiguration { +interface ReplacementConfiguration { replacement?: any; - mutationName: string; + mutationName: T; } export interface MutationLevel { @@ -39,6 +39,7 @@ export interface MutationLevel { BooleanLiteral?: BooleanLiteral[]; ConditionalExpression?: ConditionalExpression[]; EqualityOperator?: EqualityOperator[]; + LogicalOperator?: LogicalOperator[]; MethodExpression?: MethodExpression[]; ObjectLiteral?: ObjectLiteral[]; OptionalChaining?: OptionalChaining[]; diff --git a/packages/instrumenter/src/mutators/arithmetic-operator-mutator.ts b/packages/instrumenter/src/mutators/arithmetic-operator-mutator.ts index cbe6a11fb5..7f1a2ec860 100644 --- a/packages/instrumenter/src/mutators/arithmetic-operator-mutator.ts +++ b/packages/instrumenter/src/mutators/arithmetic-operator-mutator.ts @@ -1,25 +1,25 @@ import type { types } from '@babel/core'; -import { deepCloneNode } from '../util/index.js'; +import { ArithmeticOperator } from '@stryker-mutator/api/core'; -import { NodeMutatorConfiguration } from '../mutation-level/mutation-level.js'; +import { deepCloneNode } from '../util/index.js'; import { NodeMutator } from './node-mutator.js'; -const operators: NodeMutatorConfiguration = { - '+': { replacement: '-', mutationName: '+To-' }, - '-': { replacement: '+', mutationName: '-To+' }, - '*': { replacement: '/', mutationName: '*To/' }, - '/': { replacement: '*', mutationName: '/To*' }, - '%': { replacement: '*', mutationName: '%To*' }, -}; - -export const arithmeticOperatorMutator: NodeMutator = { +export const arithmeticOperatorMutator: NodeMutator = { name: 'ArithmeticOperator', + operators: { + '+': { replacement: '-', mutationName: 'AdditionOperatorNegation' }, + '-': { replacement: '+', mutationName: 'SubtractionOperatorNegation' }, + '*': { replacement: '/', mutationName: 'MultiplicationOperatorNegation' }, + '/': { replacement: '*', mutationName: 'DivisionOperatorNegation' }, + '%': { replacement: '*', mutationName: 'RemainderOperatorToMultiplicationReplacement' }, + }, + *mutate(path, levelMutations) { if (path.isBinaryExpression() && isSupported(path.node.operator, path.node) && isInMutationLevel(path.node, levelMutations)) { - const mutatedOperator = operators[path.node.operator].replacement; + const mutatedOperator = this.operators[path.node.operator].replacement; const replacement = deepCloneNode(path.node); replacement.operator = mutatedOperator; yield replacement; @@ -33,12 +33,12 @@ function isInMutationLevel(node: types.BinaryExpression, operations: string[] | return true; } - const mutatedOperator = operators[node.operator as keyof typeof operators].mutationName; + const mutatedOperator = arithmeticOperatorMutator.operators[node.operator].mutationName; return operations.some((op) => op === mutatedOperator) ?? false; } -function isSupported(operator: string, node: types.BinaryExpression): operator is keyof typeof operators { - if (!Object.keys(operators).includes(operator)) { +function isSupported(operator: string, node: types.BinaryExpression): boolean { + if (!Object.keys(arithmeticOperatorMutator.operators).includes(operator)) { return false; } diff --git a/packages/instrumenter/src/mutators/array-declaration-mutator.ts b/packages/instrumenter/src/mutators/array-declaration-mutator.ts index c29e2110e7..37ff9884d4 100644 --- a/packages/instrumenter/src/mutators/array-declaration-mutator.ts +++ b/packages/instrumenter/src/mutators/array-declaration-mutator.ts @@ -1,26 +1,31 @@ import babel from '@babel/core'; +import { ArrayDeclaration } from '@stryker-mutator/api/core'; + import { deepCloneNode } from '../util/index.js'; -import { NodeMutatorConfiguration } from '../mutation-level/mutation-level.js'; import { NodeMutator } from './node-mutator.js'; const { types } = babel; -const operators: NodeMutatorConfiguration = { - EmptyArray: { replacement: types.arrayExpression([types.stringLiteral('Stryker was here')]), mutationName: 'EmptyArray' }, - EmptyArrayConstructor: { replacement: [types.stringLiteral('Stryker was here')], mutationName: 'EmptyArrayConstructor' }, - FilledArray: { replacement: types.arrayExpression(), mutationName: 'FilledArray' }, - FilledArrayConstructor: { replacement: [], mutationName: 'FilledArrayConstructor' }, -}; - -export const arrayDeclarationMutator: NodeMutator = { +export const arrayDeclarationMutator: NodeMutator = { name: 'ArrayDeclaration', + operators: { + ArrayLiteralItemsFill: { + replacement: types.arrayExpression([types.stringLiteral('Stryker was here')]), + mutationName: 'ArrayLiteralItemsFill', + }, + ArrayConstructorItemsFill: { replacement: [types.stringLiteral('Stryker was here')], mutationName: 'ArrayConstructorItemsFill' }, + ArrayLiteralItemsRemoval: { replacement: types.arrayExpression(), mutationName: 'ArrayLiteralItemsRemoval' }, + ArrayConstructorItemsRemoval: { replacement: [], mutationName: 'ArrayConstructorItemsRemoval' }, + }, + *mutate(path, levelMutations) { // The check of the [] construct in code if (path.isArrayExpression() && isArrayInLevel(path.node, levelMutations)) { - const replacement = path.node.elements.length > 0 ? operators.FilledArray.replacement : operators.EmptyArray.replacement; + const replacement = + path.node.elements.length > 0 ? this.operators.ArrayLiteralItemsRemoval.replacement : this.operators.ArrayLiteralItemsFill.replacement; yield replacement; } // Check for the new Array() construct in code @@ -31,7 +36,9 @@ export const arrayDeclarationMutator: NodeMutator = { isArrayConstructorInLevel(path.node, levelMutations) ) { const mutatedCallArgs: babel.types.Expression[] = - path.node.arguments.length > 0 ? operators.FilledArrayConstructor.replacement : operators.EmptyArrayConstructor.replacement; + path.node.arguments.length > 0 + ? this.operators.ArrayConstructorItemsRemoval.replacement + : this.operators.ArrayConstructorItemsFill.replacement; const replacement = types.isNewExpression(path.node) ? types.newExpression(deepCloneNode(path.node.callee), mutatedCallArgs) @@ -48,8 +55,8 @@ function isArrayInLevel(node: babel.types.ArrayExpression, levelMutations: strin } return ( - (levelMutations.includes(operators.FilledArray.mutationName) && node.elements.length > 0) || - (levelMutations.includes(operators.EmptyArray.mutationName) && node.elements.length === 0) + (levelMutations.includes(arrayDeclarationMutator.operators.ArrayLiteralItemsRemoval.mutationName) && node.elements.length > 0) || + (levelMutations.includes(arrayDeclarationMutator.operators.ArrayLiteralItemsFill.mutationName) && node.elements.length === 0) ); } @@ -60,7 +67,7 @@ function isArrayConstructorInLevel(node: babel.types.CallExpression | babel.type } return ( - (levelMutations.includes(operators.FilledArrayConstructor.mutationName) && node.arguments.length > 0) || - (levelMutations.includes(operators.EmptyArrayConstructor.mutationName) && node.arguments.length === 0) + (levelMutations.includes(arrayDeclarationMutator.operators.ArrayConstructorItemsRemoval.mutationName) && node.arguments.length > 0) || + (levelMutations.includes(arrayDeclarationMutator.operators.ArrayConstructorItemsFill.mutationName) && node.arguments.length === 0) ); } diff --git a/packages/instrumenter/src/mutators/arrow-function-mutator.ts b/packages/instrumenter/src/mutators/arrow-function-mutator.ts index 541a2ee89c..79acecc081 100644 --- a/packages/instrumenter/src/mutators/arrow-function-mutator.ts +++ b/packages/instrumenter/src/mutators/arrow-function-mutator.ts @@ -2,11 +2,17 @@ import babel from '@babel/core'; const { types } = babel; +import { ArrowFunction } from '@stryker-mutator/api/core'; + import { NodeMutator } from './index.js'; -export const arrowFunctionMutator: NodeMutator = { +export const arrowFunctionMutator: NodeMutator = { name: 'ArrowFunction', + operators: { + ArrowFunctionRemoval: { mutationName: 'ArrowFunctionRemoval' }, + }, + *mutate(path, levelMutations) { if ( path.isArrowFunctionExpression() && @@ -19,6 +25,6 @@ export const arrowFunctionMutator: NodeMutator = { }, }; -function isInMutationLevel(operations: string[] | undefined): boolean { - return operations === undefined || operations.length > 0; +function isInMutationLevel(levelMutations: string[] | undefined): boolean { + return levelMutations === undefined || levelMutations.includes(arrowFunctionMutator.operators.ArrowFunctionRemoval.mutationName); } diff --git a/packages/instrumenter/src/mutators/assignment-operator-mutator.ts b/packages/instrumenter/src/mutators/assignment-operator-mutator.ts index 8cc98d78ea..9c6b5e4a26 100644 --- a/packages/instrumenter/src/mutators/assignment-operator-mutator.ts +++ b/packages/instrumenter/src/mutators/assignment-operator-mutator.ts @@ -1,32 +1,32 @@ import type { types } from '@babel/core'; -import { deepCloneNode } from '../util/index.js'; +import { AssignmentOperator } from '@stryker-mutator/api/core'; -import { NodeMutatorConfiguration } from '../mutation-level/mutation-level.js'; +import { deepCloneNode } from '../util/index.js'; import { NodeMutator } from './index.js'; -const operators: NodeMutatorConfiguration = { - '+=': { replacement: '-=', mutationName: '+=To-=' }, - '-=': { replacement: '+=', mutationName: '-=To+=' }, - '*=': { replacement: '/=', mutationName: '*=To/=' }, - '/=': { replacement: '*=', mutationName: '/=To*=' }, - '%=': { replacement: '*=', mutationName: '%=To*=' }, - '<<=': { replacement: '>>=', mutationName: '<<=To>>=' }, - '>>=': { replacement: '<<=', mutationName: '>>=To<<=' }, - '&=': { replacement: '|=', mutationName: '&=To|=' }, - '|=': { replacement: '&=', mutationName: '|=To&=' }, - '&&=': { replacement: '||=', mutationName: '&&=To||=' }, - '||=': { replacement: '&&=', mutationName: '||=To&&=' }, - '??=': { replacement: '&&=', mutationName: '??=To&&=' }, -}; - const stringTypes = Object.freeze(['StringLiteral', 'TemplateLiteral']); const stringAssignmentTypes = Object.freeze(['&&=', '||=', '??=']); -export const assignmentOperatorMutator: NodeMutator = { +export const assignmentOperatorMutator: NodeMutator = { name: 'AssignmentOperator', + operators: { + '+=': { replacement: '-=', mutationName: 'AdditionAssignmentNegation' }, + '-=': { replacement: '+=', mutationName: 'SubtractionAssignmentNegation' }, + '*=': { replacement: '/=', mutationName: 'MultiplicationAssignmentNegation' }, + '/=': { replacement: '*=', mutationName: 'DivisionAssignmentNegation' }, + '%=': { replacement: '*=', mutationName: 'RemainderAssignmentToMultiplicationReplacement' }, + '<<=': { replacement: '>>=', mutationName: 'LeftShiftAssignmentNegation' }, + '>>=': { replacement: '<<=', mutationName: 'RightShiftAssignmentNegation' }, + '&=': { replacement: '|=', mutationName: 'BitwiseAndAssignmentNegation' }, + '|=': { replacement: '&=', mutationName: 'BitwiseOrAssignmentNegation' }, + '&&=': { replacement: '||=', mutationName: 'LogicalAndAssignmentNegation' }, + '||=': { replacement: '&&=', mutationName: 'LogicalOrAssignmentNegation' }, + '??=': { replacement: '&&=', mutationName: 'NullishCoalescingAssignmentToLogicalAndReplacement' }, + }, + *mutate(path, levelMutations) { if ( path.isAssignmentExpression() && @@ -34,7 +34,7 @@ export const assignmentOperatorMutator: NodeMutator = { isSupported(path.node) && isInMutationLevel(path.node, levelMutations) ) { - const mutatedOperator = operators[path.node.operator].replacement; + const mutatedOperator = this.operators[path.node.operator].replacement; const replacementOperator = deepCloneNode(path.node); replacementOperator.operator = mutatedOperator; yield replacementOperator; @@ -46,12 +46,12 @@ function isInMutationLevel(node: types.AssignmentExpression, operations: string[ if (operations === undefined) { return true; } - const { mutationName } = operators[node.operator]; + const { mutationName } = assignmentOperatorMutator.operators[node.operator]; return operations.some((op) => op === mutationName); } -function isSupportedAssignmentOperator(operator: string): operator is keyof typeof operators { - return Object.keys(operators).includes(operator); +function isSupportedAssignmentOperator(operator: string): boolean { + return Object.keys(assignmentOperatorMutator.operators).includes(operator); } function isSupported(node: types.AssignmentExpression): boolean { diff --git a/packages/instrumenter/src/mutators/block-statement-mutator.ts b/packages/instrumenter/src/mutators/block-statement-mutator.ts index a7a7153cf4..188d58eb5a 100644 --- a/packages/instrumenter/src/mutators/block-statement-mutator.ts +++ b/packages/instrumenter/src/mutators/block-statement-mutator.ts @@ -1,14 +1,20 @@ import babel, { type NodePath } from '@babel/core'; +import { BlockStatement } from '@stryker-mutator/api/core'; + import { NodeMutator } from './node-mutator.js'; const { types } = babel; -export const blockStatementMutator: NodeMutator = { +export const blockStatementMutator: NodeMutator = { name: 'BlockStatement', - *mutate(path, options) { - if (path.isBlockStatement() && isValid(path) && isInMutationLevel(options)) { + operators: { + BlockStatementRemoval: { mutationName: 'BlockStatementRemoval' }, + }, + + *mutate(path, levelMutations) { + if (path.isBlockStatement() && isValid(path) && isInMutationLevel(levelMutations)) { yield types.blockStatement([]); } }, @@ -70,6 +76,6 @@ function hasSuperExpressionOnFirstLine(constructor: NodePath 0; +function isInMutationLevel(levelMutations: string[] | undefined): boolean { + return levelMutations === undefined || levelMutations.includes(blockStatementMutator.operators.BlockStatementRemoval.mutationName); } diff --git a/packages/instrumenter/src/mutators/boolean-literal-mutator.ts b/packages/instrumenter/src/mutators/boolean-literal-mutator.ts index 61a1c82f34..41c21e7a21 100644 --- a/packages/instrumenter/src/mutators/boolean-literal-mutator.ts +++ b/packages/instrumenter/src/mutators/boolean-literal-mutator.ts @@ -1,22 +1,22 @@ import babel from '@babel/core'; +import { BooleanLiteral } from '@stryker-mutator/api/core'; + import { deepCloneNode } from '../util/index.js'; const { types } = babel; -import { NodeMutatorConfiguration } from '../mutation-level/mutation-level.js'; - import { NodeMutator } from './index.js'; -const operators: NodeMutatorConfiguration = { - true: { replacement: false, mutationName: 'TrueToFalse' }, - false: { replacement: true, mutationName: 'FalseToTrue' }, - '!': { replacement: '', mutationName: 'RemoveNegation' }, -}; - -export const booleanLiteralMutator: NodeMutator = { +export const booleanLiteralMutator: NodeMutator = { name: 'BooleanLiteral', + operators: { + true: { replacement: false, mutationName: 'TrueLiteralNegation' }, + false: { replacement: true, mutationName: 'FalseLiteralNegation' }, + '!': { replacement: '', mutationName: 'LogicalNotRemoval' }, + }, + *mutate(path, levelMutations) { if (isInMutationLevel(path, levelMutations)) { if (path.isBooleanLiteral()) { @@ -34,13 +34,13 @@ function isInMutationLevel(path: any, levelMutations: string[] | undefined): boo return true; } if (path.isBooleanLiteral()) { - const { mutationName: mutatorName } = operators[path.node.value as keyof typeof operators]; + const { mutationName: mutatorName } = booleanLiteralMutator.operators[path.node.value]; return levelMutations.some((lit) => lit === mutatorName); } return ( path.isUnaryExpression() && path.node.operator === '!' && path.node.prefix && - levelMutations.some((lit: string) => lit === operators['!'].mutationName) + levelMutations.some((lit: string) => lit === booleanLiteralMutator.operators['!'].mutationName) ); } diff --git a/packages/instrumenter/src/mutators/conditional-expression-mutator.ts b/packages/instrumenter/src/mutators/conditional-expression-mutator.ts index a0c795d985..f3f657cc8a 100644 --- a/packages/instrumenter/src/mutators/conditional-expression-mutator.ts +++ b/packages/instrumenter/src/mutators/conditional-expression-mutator.ts @@ -1,8 +1,8 @@ import babel, { type NodePath } from '@babel/core'; -import { deepCloneNode } from '../util/index.js'; +import { ConditionalExpression } from '@stryker-mutator/api/core'; -import { NodeMutatorConfiguration } from '../mutation-level/mutation-level.js'; +import { deepCloneNode } from '../util/index.js'; import { NodeMutator } from './node-mutator.js'; @@ -10,38 +10,47 @@ const booleanOperators = Object.freeze(['!=', '!==', '&&', '<', '<=', '==', '=== const { types } = babel; -const operators: NodeMutatorConfiguration = { - BooleanExpressionToFalse: { replacement: types.booleanLiteral(false), mutationName: 'BooleanExpressionToFalse' }, - BooleanExpressionToTrue: { replacement: types.booleanLiteral(true), mutationName: 'BooleanExpressionToTrue' }, - DoWhileLoopToFalse: { replacement: types.booleanLiteral(false), mutationName: 'DoWhileLoopToFalse' }, - ForLoopToFalse: { replacement: types.booleanLiteral(false), mutationName: 'ForLoopToFalse' }, - IfToFalse: { replacement: types.booleanLiteral(false), mutationName: 'IfToFalse' }, - IfToTrue: { replacement: types.booleanLiteral(true), mutationName: 'IfToTrue' }, - WhileLoopToFalse: { replacement: types.booleanLiteral(false), mutationName: 'WhileLoopToFalse' }, - SwitchToEmpty: { replacement: [], mutationName: 'SwitchToEmpty' }, -}; - -export const conditionalExpressionMutator: NodeMutator = { +export const conditionalExpressionMutator: NodeMutator = { name: 'ConditionalExpression', + operators: { + BooleanExpressionToFalseReplacement: { replacement: types.booleanLiteral(false), mutationName: 'BooleanExpressionToFalseReplacement' }, + BooleanExpressionToTrueReplacement: { replacement: types.booleanLiteral(true), mutationName: 'BooleanExpressionToTrueReplacement' }, + DoWhileLoopConditionToFalseReplacement: { replacement: types.booleanLiteral(false), mutationName: 'DoWhileLoopConditionToFalseReplacement' }, + ForLoopConditionToFalseReplacement: { replacement: types.booleanLiteral(false), mutationName: 'ForLoopConditionToFalseReplacement' }, + IfConditionToFalseReplacement: { replacement: types.booleanLiteral(false), mutationName: 'IfConditionToFalseReplacement' }, + IfConditionToTrueReplacement: { replacement: types.booleanLiteral(true), mutationName: 'IfConditionToTrueReplacement' }, + WhileLoopConditionToFalseReplacement: { replacement: types.booleanLiteral(false), mutationName: 'WhileLoopConditionToFalseReplacement' }, + SwitchStatementBodyRemoval: { replacement: [], mutationName: 'SwitchStatementBodyRemoval' }, + }, + *mutate(path, levelMutations) { if (isTestOfLoop(path)) { - if (isTestOfWhileLoop(path) && (levelMutations === undefined || levelMutations.includes(operators.WhileLoopToFalse.mutationName))) { - yield operators.WhileLoopToFalse.replacement; + if ( + isTestOfWhileLoop(path) && + (levelMutations === undefined || levelMutations.includes(this.operators.WhileLoopConditionToFalseReplacement.mutationName)) + ) { + yield this.operators.WhileLoopConditionToFalseReplacement.replacement; } - if (isTestOfDoWhileLoop(path) && (levelMutations === undefined || levelMutations.includes(operators.DoWhileLoopToFalse.mutationName))) { - yield operators.DoWhileLoopToFalse.replacement; + if ( + isTestOfDoWhileLoop(path) && + (levelMutations === undefined || levelMutations.includes(this.operators.DoWhileLoopConditionToFalseReplacement.mutationName)) + ) { + yield this.operators.DoWhileLoopConditionToFalseReplacement.replacement; } - if (isTestOfForLoop(path) && (levelMutations === undefined || levelMutations.includes(operators.ForLoopToFalse.mutationName))) { - yield operators.ForLoopToFalse.replacement; + if ( + isTestOfForLoop(path) && + (levelMutations === undefined || levelMutations.includes(this.operators.ForLoopConditionToFalseReplacement.mutationName)) + ) { + yield this.operators.ForLoopConditionToFalseReplacement.replacement; } } else if (isTestOfCondition(path)) { - if (levelMutations === undefined || levelMutations.includes(operators.IfToTrue.mutationName)) { - yield operators.IfToTrue.replacement; + if (levelMutations === undefined || levelMutations.includes(this.operators.IfConditionToTrueReplacement.mutationName)) { + yield this.operators.IfConditionToTrueReplacement.replacement; } - if (levelMutations === undefined || levelMutations.includes(operators.IfToFalse.mutationName)) { - yield operators.IfToFalse.replacement; + if (levelMutations === undefined || levelMutations.includes(this.operators.IfConditionToFalseReplacement.mutationName)) { + yield this.operators.IfConditionToFalseReplacement.replacement; } } else if (isBooleanExpression(path)) { if (path.parent?.type === 'LogicalExpression') { @@ -49,8 +58,8 @@ export const conditionalExpressionMutator: NodeMutator = { // has the same behavior as the (true) mutator, handled in the // isTestOfCondition branch above if (path.parent.operator === '||') { - if (levelMutations === undefined || levelMutations.includes(operators.BooleanExpressionToFalse.mutationName)) { - yield operators.BooleanExpressionToFalse.replacement; + if (levelMutations === undefined || levelMutations.includes(this.operators.BooleanExpressionToFalseReplacement.mutationName)) { + yield this.operators.BooleanExpressionToFalseReplacement.replacement; } return; } @@ -58,29 +67,29 @@ export const conditionalExpressionMutator: NodeMutator = { // has the same behavior as the (false) mutator, handled in the // isTestOfCondition branch above if (path.parent.operator === '&&') { - if (levelMutations === undefined || levelMutations.includes(operators.BooleanExpressionToTrue.mutationName)) { - yield operators.BooleanExpressionToTrue.replacement; + if (levelMutations === undefined || levelMutations.includes(this.operators.BooleanExpressionToTrueReplacement.mutationName)) { + yield this.operators.BooleanExpressionToTrueReplacement.replacement; } return; } } - if (levelMutations === undefined || levelMutations.includes(operators.BooleanExpressionToTrue.mutationName)) { - yield operators.BooleanExpressionToTrue.replacement; + if (levelMutations === undefined || levelMutations.includes(this.operators.BooleanExpressionToTrueReplacement.mutationName)) { + yield this.operators.BooleanExpressionToTrueReplacement.replacement; } - if (levelMutations === undefined || levelMutations.includes(operators.BooleanExpressionToFalse.mutationName)) { - yield operators.BooleanExpressionToFalse.replacement; + if (levelMutations === undefined || levelMutations.includes(this.operators.BooleanExpressionToFalseReplacement.mutationName)) { + yield this.operators.BooleanExpressionToFalseReplacement.replacement; } } else if (path.isForStatement() && !path.node.test) { - if (levelMutations === undefined || levelMutations.includes(operators.ForLoopToFalse.mutationName)) { + if (levelMutations === undefined || levelMutations.includes(this.operators.ForLoopConditionToFalseReplacement.mutationName)) { const replacement = deepCloneNode(path.node); - replacement.test = operators.ForLoopToFalse.replacement; + replacement.test = this.operators.ForLoopConditionToFalseReplacement.replacement; yield replacement; } } else if (path.isSwitchCase() && path.node.consequent.length > 0) { // if not a fallthrough case - if (levelMutations === undefined || levelMutations.includes(operators.SwitchToEmpty.mutationName)) { + if (levelMutations === undefined || levelMutations.includes(this.operators.SwitchStatementBodyRemoval.mutationName)) { const replacement = deepCloneNode(path.node); - replacement.consequent = operators.SwitchToEmpty.replacement; + replacement.consequent = this.operators.SwitchStatementBodyRemoval.replacement; yield replacement; } } diff --git a/packages/instrumenter/src/mutators/equality-operator-mutator.ts b/packages/instrumenter/src/mutators/equality-operator-mutator.ts index 3fa6247132..a7c0338d7a 100644 --- a/packages/instrumenter/src/mutators/equality-operator-mutator.ts +++ b/packages/instrumenter/src/mutators/equality-operator-mutator.ts @@ -1,43 +1,36 @@ import babel, { types } from '@babel/core'; -import { NodeMutatorMultiConfiguration } from '../mutation-level/mutation-level.js'; +import { EqualityOperator } from '@stryker-mutator/api/core'; import { NodeMutator } from './node-mutator.js'; const { types: t } = babel; -const operators: NodeMutatorMultiConfiguration = { - '<': [ - { replacement: '<=', mutationName: '=', mutationName: '=' }, - ], - '<=': [ - { replacement: '<', mutationName: '<=To<' }, - { replacement: '>', mutationName: '<=To>' }, - ], - '>': [ - { replacement: '>=', mutationName: '>To>=' }, - { replacement: '<=', mutationName: '>To<=' }, - ], - '>=': [ - { replacement: '>', mutationName: '>=To>' }, - { replacement: '<', mutationName: '>=To<' }, - ], - '==': [{ replacement: '!=', mutationName: '==To!=' }], - '!=': [{ replacement: '==', mutationName: '!=To==' }], - '===': [{ replacement: '!==', mutationName: '===To!==' }], - '!==': [{ replacement: '===', mutationName: '!==To===' }], -}; - -function isEqualityOperator(operator: string): operator is keyof typeof operators { - return Object.keys(operators).includes(operator); -} -export const equalityOperatorMutator: NodeMutator = { +export const equalityOperatorMutator: NodeMutator = { name: 'EqualityOperator', - *mutate(path, operations) { + operators: { + '=': { replacement: '>=', mutationName: 'LessThanOperatorNegation' }, + + '<=To<': { replacement: '<', mutationName: 'LessThanEqualOperatorBoundary' }, + '<=To>': { replacement: '>', mutationName: 'LessThanEqualOperatorNegation' }, + + '>To>=': { replacement: '>=', mutationName: 'GreaterThanOperatorBoundary' }, + '>To<=': { replacement: '<=', mutationName: 'GreaterThanOperatorNegation' }, + + '>=To>': { replacement: '>', mutationName: 'GreaterThanEqualOperatorBoundary' }, + '>=To<': { replacement: '<', mutationName: 'GreaterThanEqualOperatorNegation' }, + + '==To!=': { replacement: '!=', mutationName: 'EqualityOperatorNegation' }, + '!=To==': { replacement: '==', mutationName: 'InequalityOperatorNegation' }, + '===To!==': { replacement: '!==', mutationName: 'StrictEqualityOperatorNegation' }, + '!==To===': { replacement: '===', mutationName: 'StrictInequalityOperatorNegation' }, + }, + + *mutate(path, levelMutations) { if (path.isBinaryExpression() && isEqualityOperator(path.node.operator)) { - const allMutations = filterMutationLevel(path.node, operations); + const allMutations = filterMutationLevel(path.node, levelMutations); // throw new Error(allMutations.toString()); for (const mutableOperator of allMutations) { const replacementOperator = t.cloneNode(path.node, true); @@ -48,10 +41,17 @@ export const equalityOperatorMutator: NodeMutator = { }, }; -function filterMutationLevel(node: types.BinaryExpression, levelMutations: string[] | undefined) { - const allMutations = operators[node.operator as keyof typeof operators]; +function isEqualityOperator(operator: string): operator is keyof typeof equalityOperatorMutator.operators { + return Object.keys(equalityOperatorMutator.operators).some((k) => k.startsWith(operator + 'To')); +} +function filterMutationLevel(node: types.BinaryExpression, levelMutations: string[] | undefined) { // Nothing allowed, so return an empty array + + const allMutations = Object.keys(equalityOperatorMutator.operators) + .filter((k) => k.startsWith(node.operator + 'To')) + .map((k) => equalityOperatorMutator.operators[k]); + if (levelMutations === undefined) { return allMutations; } diff --git a/packages/instrumenter/src/mutators/logical-operator-mutator.ts b/packages/instrumenter/src/mutators/logical-operator-mutator.ts index a7ff18f273..6de4d4c339 100644 --- a/packages/instrumenter/src/mutators/logical-operator-mutator.ts +++ b/packages/instrumenter/src/mutators/logical-operator-mutator.ts @@ -1,20 +1,21 @@ -import { NodeMutatorConfiguration } from '../mutation-level/mutation-level.js'; +import { LogicalOperator } from '@stryker-mutator/api/core'; + import { deepCloneNode } from '../util/index.js'; import { NodeMutator } from './index.js'; -const operators: NodeMutatorConfiguration = { - '&&': { replacement: '||', mutationName: '&&To||' }, - '||': { replacement: '&&', mutationName: '||To&&' }, - '??': { replacement: '&&', mutationName: '??To&&' }, -}; - -export const logicalOperatorMutator: NodeMutator = { +export const logicalOperatorMutator: NodeMutator = { name: 'LogicalOperator', + operators: { + '&&': { replacement: '||', mutationName: 'LogicalAndOperatorNegation' }, + '||': { replacement: '&&', mutationName: 'LogicalOrOperatorNegation' }, + '??': { replacement: '&&', mutationName: 'NullishCoalescingOperatorToLogicalAndReplacement' }, + }, + *mutate(path, levelMutations) { if (path.isLogicalExpression() && isSupported(path.node.operator) && isInMutationLevel(path.node.operator, levelMutations)) { - const mutatedOperator = operators[path.node.operator].replacement; + const mutatedOperator = this.operators[path.node.operator].replacement; const replacementOperator = deepCloneNode(path.node); replacementOperator.operator = mutatedOperator; @@ -23,10 +24,10 @@ export const logicalOperatorMutator: NodeMutator = { }, }; -function isSupported(operator: string): operator is keyof typeof operators { - return Object.keys(operators).includes(operator); +function isSupported(operator: string): operator is keyof typeof logicalOperatorMutator.operators { + return Object.keys(logicalOperatorMutator.operators).includes(operator); } -function isInMutationLevel(operator: string, levelMutations: string[] | undefined): operator is keyof typeof operators { - return levelMutations === undefined || levelMutations.some((op) => op.startsWith(operator)); +function isInMutationLevel(operator: string, levelMutations: string[] | undefined): boolean { + return levelMutations === undefined || levelMutations.includes(logicalOperatorMutator.operators[operator].mutationName as string); } diff --git a/packages/instrumenter/src/mutators/method-expression-mutator.ts b/packages/instrumenter/src/mutators/method-expression-mutator.ts index d43847919b..f04f845e9c 100644 --- a/packages/instrumenter/src/mutators/method-expression-mutator.ts +++ b/packages/instrumenter/src/mutators/method-expression-mutator.ts @@ -1,40 +1,39 @@ import babel from '@babel/core'; -import { deepCloneNode } from '../util/index.js'; +import { MethodExpression } from '@stryker-mutator/api/core'; -import { NodeMutatorConfiguration } from '../mutation-level/mutation-level.js'; +import { deepCloneNode } from '../util/index.js'; import { NodeMutator } from './node-mutator.js'; const { types } = babel; -// prettier-ignore -const operators: NodeMutatorConfiguration = { - 'charAt': { replacement: null, mutationName: 'removeCharAt' }, - 'endsWith': { replacement: 'startsWith', mutationName: 'endsWithToStartsWith' }, - 'startsWith': { replacement: 'endsWith', mutationName: 'startsWithToEndsWith' }, - 'every': { replacement: 'some', mutationName: 'everyToSome' }, - 'some': { replacement: 'every', mutationName: 'someToEvery' }, - 'filter': { replacement: null, mutationName: 'removeFilter' }, - 'reverse': { replacement: null, mutationName: 'removeReverse' }, - 'slice': { replacement: null, mutationName: 'removeSlice' }, - 'sort': { replacement: null, mutationName: 'removeSort' }, - 'substr': { replacement: null, mutationName: 'removeSubstr' }, - 'substring': { replacement: null, mutationName: 'removeSubstring' }, - 'toLocaleLowerCase': { replacement: 'toLocaleUpperCase', mutationName: 'toLocaleLowerCaseToToLocaleUpperCase' }, - 'toLocaleUpperCase': { replacement: 'toLocaleLowerCase', mutationName: 'toLocaleUpperCaseToToLocaleLowerCase' }, - 'toLowerCase': { replacement: 'toUpperCase', mutationName: 'toLowerCaseToToUpperCase' }, - 'toUpperCase': { replacement: 'toLowerCase', mutationName: 'toUpperCaseToToLowerCase' }, - 'trim': { replacement: null, mutationName: 'removeTrim' }, - 'trimEnd': { replacement: 'trimStart', mutationName: 'trimEndToTrimStart' }, - 'trimStart': { replacement: 'trimEnd', mutationName: 'trimStartToTrimEnd' }, - 'min': { replacement: 'max', mutationName: 'minToMax' }, - 'max': { replacement: 'min', mutationName: 'maxToMin' }, -}; - -export const methodExpressionMutator: NodeMutator = { +export const methodExpressionMutator: NodeMutator = { name: 'MethodExpression', + operators: { + charAt: { replacement: null, mutationName: 'CharAtMethodCallRemoval' }, + endsWith: { replacement: 'startsWith', mutationName: 'EndsWithMethodCallNegation' }, + startsWith: { replacement: 'endsWith', mutationName: 'StartsWithMethodCallNegation' }, + every: { replacement: 'some', mutationName: 'EveryMethodCallNegation' }, + some: { replacement: 'every', mutationName: 'SomeMethodCallNegation' }, + filter: { replacement: null, mutationName: 'FilterMethodCallRemoval' }, + reverse: { replacement: null, mutationName: 'ReverseMethodCallRemoval' }, + slice: { replacement: null, mutationName: 'SliceMethodCallRemoval' }, + sort: { replacement: null, mutationName: 'SortMethodCallRemoval' }, + substr: { replacement: null, mutationName: 'SubstrMethodCallRemoval' }, + substring: { replacement: null, mutationName: 'SubstringMethodCallRemoval' }, + toLocaleLowerCase: { replacement: 'toLocaleUpperCase', mutationName: 'ToLocaleLowerCaseMethodCallNegation' }, + toLocaleUpperCase: { replacement: 'toLocaleLowerCase', mutationName: 'ToLocaleUpperCaseMethodCallNegation' }, + toLowerCase: { replacement: 'toUpperCase', mutationName: 'ToLowerCaseMethodCallNegation' }, + toUpperCase: { replacement: 'toLowerCase', mutationName: 'ToUpperCaseMethodCallNegation' }, + trim: { replacement: null, mutationName: 'TrimMethodCallRemoval' }, + trimEnd: { replacement: 'trimStart', mutationName: 'TrimEndMethodCallNegation' }, + trimStart: { replacement: 'trimEnd', mutationName: 'TrimStartMethodCallNegation' }, + min: { replacement: 'max', mutationName: 'MinMethodCallNegation' }, + max: { replacement: 'min', mutationName: 'MaxMethodCallNegation' }, + }, + *mutate(path, levelMutations) { // In case `operations` is undefined, any checks will short-circuit to true and allow the mutation @@ -47,7 +46,7 @@ export const methodExpressionMutator: NodeMutator = { return; } - const mutation = operators[callee.property.name]; + const mutation = this.operators[callee.property.name]; if (mutation === undefined) { // Function is not known in `operators`, so no mutations return; diff --git a/packages/instrumenter/src/mutators/mutate.ts b/packages/instrumenter/src/mutators/mutate.ts index 7350fead57..cc8153f1db 100644 --- a/packages/instrumenter/src/mutators/mutate.ts +++ b/packages/instrumenter/src/mutators/mutate.ts @@ -1,3 +1,5 @@ +import { MutationLevel } from '../mutation-level/mutation-level.js'; + import { arithmeticOperatorMutator } from './arithmetic-operator-mutator.js'; import { NodeMutator } from './node-mutator.js'; import { blockStatementMutator } from './block-statement-mutator.js'; @@ -16,7 +18,7 @@ import { regexMutator } from './regex-mutator.js'; import { optionalChainingMutator } from './optional-chaining-mutator.js'; import { assignmentOperatorMutator } from './assignment-operator-mutator.js'; -export const allMutators: NodeMutator[] = [ +export const allMutators: Array> = [ arithmeticOperatorMutator, arrayDeclarationMutator, arrowFunctionMutator, diff --git a/packages/instrumenter/src/mutators/mutator-options.ts b/packages/instrumenter/src/mutators/mutator-options.ts index a9e684da9a..3cb669a20c 100644 --- a/packages/instrumenter/src/mutators/mutator-options.ts +++ b/packages/instrumenter/src/mutators/mutator-options.ts @@ -1,7 +1,7 @@ -import { MutatorDefinition } from '@stryker-mutator/api/core'; +import { MutationSpecification } from '@stryker-mutator/api/core'; export interface MutatorOptions { - includedMutations: MutatorDefinition[]; - excludedMutations: MutatorDefinition[]; + includedMutations: MutationSpecification[]; + excludedMutations: MutationSpecification[]; noHeader?: boolean; } diff --git a/packages/instrumenter/src/mutators/node-mutator.ts b/packages/instrumenter/src/mutators/node-mutator.ts index e1b5f2d039..5a87c2b4df 100644 --- a/packages/instrumenter/src/mutators/node-mutator.ts +++ b/packages/instrumenter/src/mutators/node-mutator.ts @@ -1,9 +1,9 @@ import type { types, NodePath } from '@babel/core'; -import { NodeMutatorConfiguration } from '../mutation-level/mutation-level.js'; +import { NodeMutatorConfiguration, MutationLevel } from '../mutation-level/mutation-level.js'; -export interface NodeMutator { +export interface NodeMutator { mutate(path: NodePath, levelMutations: string[] | undefined): Iterable; readonly name: string; - readonly operators?: NodeMutatorConfiguration; + operators: NodeMutatorConfiguration; } diff --git a/packages/instrumenter/src/mutators/object-literal-mutator.ts b/packages/instrumenter/src/mutators/object-literal-mutator.ts index 8660fca0e0..9137324d18 100644 --- a/packages/instrumenter/src/mutators/object-literal-mutator.ts +++ b/packages/instrumenter/src/mutators/object-literal-mutator.ts @@ -1,19 +1,27 @@ import babel from '@babel/core'; +import { ObjectLiteral } from '@stryker-mutator/api/core'; + import { NodeMutator } from './index.js'; const { types } = babel; -export const objectLiteralMutator: NodeMutator = { +export const objectLiteralMutator: NodeMutator = { name: 'ObjectLiteral', - *mutate(path, options) { - if (path.isObjectExpression() && path.node.properties.length > 0 && isInMutationLevel(options)) { + operators: { + ObjectLiteralPropertiesRemoval: { mutationName: 'ObjectLiteralPropertiesRemoval' }, + }, + + *mutate(path, levelMutations) { + if (path.isObjectExpression() && path.node.properties.length > 0 && isInMutationLevel(levelMutations)) { yield types.objectExpression([]); } }, }; -function isInMutationLevel(operations: string[] | undefined): boolean { - return operations === undefined || operations.length > 0; +function isInMutationLevel(levelMutations: string[] | undefined): boolean { + return ( + levelMutations === undefined || levelMutations.includes(objectLiteralMutator.operators.ObjectLiteralPropertiesRemoval.mutationName as string) + ); } diff --git a/packages/instrumenter/src/mutators/optional-chaining-mutator.ts b/packages/instrumenter/src/mutators/optional-chaining-mutator.ts index baa392e0dd..e30962e94e 100644 --- a/packages/instrumenter/src/mutators/optional-chaining-mutator.ts +++ b/packages/instrumenter/src/mutators/optional-chaining-mutator.ts @@ -1,6 +1,6 @@ import babel from '@babel/core'; -import { NodeMutatorConfiguration } from '../mutation-level/mutation-level.js'; +import { OptionalChaining } from '@stryker-mutator/api/core'; import { NodeMutator } from './index.js'; @@ -19,19 +19,22 @@ const { types: t } = babel; * foo?.() -> foo() */ -const operators: NodeMutatorConfiguration = { - OptionalCallExpression: { mutationName: 'OptionalCallExpression' }, - OptionalMemberExpression: { mutationName: 'OptionalMemberExpression' }, -}; - -export const optionalChainingMutator: NodeMutator = { +export const optionalChainingMutator: NodeMutator = { name: 'OptionalChaining', + operators: { + OptionalCallExpressionOptionalRemoval: { mutationName: 'OptionalCallExpressionOptionalRemoval' }, + OptionalMemberExpressionOptionalRemoval: { mutationName: 'OptionalMemberExpressionOptionalRemoval' }, + OptionalComputedMemberExpressionOptionalRemoval: { mutationName: 'OptionalComputedMemberExpressionOptionalRemoval' }, + }, + *mutate(path, levelMutations) { if ( path.isOptionalMemberExpression() && path.node.optional && - (levelMutations === undefined || levelMutations.includes(operators.OptionalMemberExpression.mutationName)) + (levelMutations === undefined || + (!path.node.computed && levelMutations.includes(this.operators.OptionalMemberExpressionOptionalRemoval.mutationName)) || + (path.node.computed && levelMutations.includes(this.operators.OptionalComputedMemberExpressionOptionalRemoval.mutationName))) ) { yield t.optionalMemberExpression( t.cloneNode(path.node.object, true), @@ -43,7 +46,7 @@ export const optionalChainingMutator: NodeMutator = { if ( path.isOptionalCallExpression() && path.node.optional && - (levelMutations === undefined || levelMutations.includes(operators.OptionalCallExpression.mutationName)) + (levelMutations === undefined || levelMutations.includes(this.operators.OptionalCallExpressionOptionalRemoval.mutationName)) ) { yield t.optionalCallExpression( t.cloneNode(path.node.callee, true), diff --git a/packages/instrumenter/src/mutators/regex-mutator.ts b/packages/instrumenter/src/mutators/regex-mutator.ts index 310d7edd28..467808f226 100644 --- a/packages/instrumenter/src/mutators/regex-mutator.ts +++ b/packages/instrumenter/src/mutators/regex-mutator.ts @@ -1,6 +1,8 @@ import babel, { NodePath, type types as t } from '@babel/core'; import * as weaponRegex from 'weapon-regex'; +import { Regex } from '@stryker-mutator/api/core'; + import { NodeMutator } from './index.js'; const { types } = babel; @@ -29,9 +31,13 @@ function getFlags(path: NodePath): string | undefined { const weaponRegexOptions: weaponRegex.MutationOptions = { mutationLevels: [1] }; -export const regexMutator: NodeMutator = { +export const regexMutator: NodeMutator = { name: 'Regex', + operators: { + RegexRemoval: { mutationName: 'RegexRemoval' }, + }, + *mutate(path, options) { if (path.isRegExpLiteral() && isInMutationLevel(options)) { for (const replacementPattern of mutatePattern(path.node.pattern, path.node.flags)) { @@ -60,6 +66,6 @@ function mutatePattern(pattern: string, flags: string | undefined): string[] { return []; } -function isInMutationLevel(operations: string[] | undefined): boolean { - return operations === undefined || operations.length > 0; +function isInMutationLevel(levelMutations: string[] | undefined): boolean { + return levelMutations === undefined || levelMutations.includes(regexMutator.operators.RegexRemoval.mutationName); } diff --git a/packages/instrumenter/src/mutators/string-literal-mutator.ts b/packages/instrumenter/src/mutators/string-literal-mutator.ts index d76a043d78..ed168c2a68 100644 --- a/packages/instrumenter/src/mutators/string-literal-mutator.ts +++ b/packages/instrumenter/src/mutators/string-literal-mutator.ts @@ -1,43 +1,56 @@ import babel, { type NodePath } from '@babel/core'; -import { NodeMutatorConfiguration } from '../mutation-level/mutation-level.js'; +import { StringLiteral } from '@stryker-mutator/api/core'; import { NodeMutator } from './node-mutator.js'; const { types } = babel; -const operators: NodeMutatorConfiguration = { - FillString: { replacement: types.stringLiteral('Stryker was here!'), mutationName: 'FillString' }, - EmptyString: { replacement: types.stringLiteral(''), mutationName: 'EmptyString' }, - EmptyInterpolation: { replacement: types.templateLiteral([types.templateElement({ raw: '' })], []), mutationName: 'EmptyInterpolation' }, - FillInterpolation: { - replacement: types.templateLiteral([types.templateElement({ raw: 'Stryker was here!' })], []), - mutationName: 'FillInterpolation', - }, -}; - -export const stringLiteralMutator: NodeMutator = { +export const stringLiteralMutator: NodeMutator = { name: 'StringLiteral', + operators: { + EmptyStringLiteralToFilledReplacement: { + replacement: types.stringLiteral('Stryker was here!'), + mutationName: 'EmptyStringLiteralToFilledReplacement', + }, + FilledStringLiteralToEmptyReplacement: { + replacement: types.stringLiteral(''), + mutationName: 'FilledStringLiteralToEmptyReplacement', + }, + FilledInterpolatedStringToEmptyReplacement: { + replacement: types.templateLiteral([types.templateElement({ raw: '' })], []), + mutationName: 'FilledInterpolatedStringToEmptyReplacement', + }, + EmptyInterpolatedStringToFilledReplacement: { + replacement: types.templateLiteral([types.templateElement({ raw: 'Stryker was here!' })], []), + mutationName: 'EmptyInterpolatedStringToFilledReplacement', + }, + }, + *mutate(path, levelMutations) { if (path.isTemplateLiteral()) { const stringIsEmpty = path.node.quasis.length === 1 && path.node.quasis[0].value.raw.length === 0; if ( levelMutations === undefined || - (stringIsEmpty && levelMutations.includes(operators.FillInterpolation.mutationName)) || - (!stringIsEmpty && levelMutations.includes(operators.EmptyInterpolation.mutationName)) + (stringIsEmpty && levelMutations.includes(this.operators.EmptyInterpolatedStringToFilledReplacement.mutationName)) || + (!stringIsEmpty && levelMutations.includes(this.operators.FilledInterpolatedStringToEmptyReplacement.mutationName)) ) { - yield stringIsEmpty ? operators.FillInterpolation.replacement : operators.EmptyInterpolation.replacement; + yield stringIsEmpty + ? this.operators.EmptyInterpolatedStringToFilledReplacement.replacement + : this.operators.FilledInterpolatedStringToEmptyReplacement.replacement; } } if (path.isStringLiteral() && isValidParent(path)) { const stringIsEmpty = path.node.value.length === 0; if ( levelMutations === undefined || - (stringIsEmpty && levelMutations.includes(operators.FillString.mutationName)) || - (!stringIsEmpty && levelMutations.includes(operators.EmptyString.mutationName)) + (stringIsEmpty && levelMutations.includes(this.operators.EmptyStringLiteralToFilledReplacement.mutationName)) || + (!stringIsEmpty && levelMutations.includes(this.operators.FilledStringLiteralToEmptyReplacement.mutationName)) ) { - yield stringIsEmpty ? operators.FillString.replacement : operators.EmptyString.replacement; + yield stringIsEmpty + ? this.operators.EmptyStringLiteralToFilledReplacement.replacement + : this.operators.FilledStringLiteralToEmptyReplacement.replacement; } } }, diff --git a/packages/instrumenter/src/mutators/unary-operator-mutator.ts b/packages/instrumenter/src/mutators/unary-operator-mutator.ts index a5fd612402..7ddb553264 100644 --- a/packages/instrumenter/src/mutators/unary-operator-mutator.ts +++ b/packages/instrumenter/src/mutators/unary-operator-mutator.ts @@ -1,25 +1,25 @@ import babel from '@babel/core'; -import { deepCloneNode } from '../util/index.js'; +import { UnaryOperator } from '@stryker-mutator/api/core'; -import { NodeMutatorConfiguration } from '../mutation-level/mutation-level.js'; +import { deepCloneNode } from '../util/index.js'; import { NodeMutator } from './index.js'; const { types } = babel; -const operators: NodeMutatorConfiguration = { - '+': { replacement: '-', mutationName: '+To-' }, - '-': { replacement: '+', mutationName: '-To+' }, - '~': { replacement: '', mutationName: 'remove~' }, -}; - -export const unaryOperatorMutator: NodeMutator = { +export const unaryOperatorMutator: NodeMutator = { name: 'UnaryOperator', + operators: { + '+': { replacement: '-', mutationName: 'UnaryPlusOperatorNegation' }, + '-': { replacement: '+', mutationName: 'UnaryMinOperatorNegation' }, + '~': { replacement: '', mutationName: 'UnaryBitwiseOrRemoval' }, + }, + *mutate(path, levelMutations) { if (path.isUnaryExpression() && isSupported(path.node.operator) && path.node.prefix) { - const mutation = operators[path.node.operator]; + const mutation = this.operators[path.node.operator]; if (levelMutations !== undefined && !levelMutations.includes(mutation.mutationName)) { // Mutator not allowed by MutationLevel @@ -35,6 +35,6 @@ export const unaryOperatorMutator: NodeMutator = { }, }; -function isSupported(operator: string): operator is keyof typeof operators { - return operator in operators; +function isSupported(operator: string): operator is keyof typeof unaryOperatorMutator.operators { + return operator in unaryOperatorMutator.operators; } diff --git a/packages/instrumenter/src/mutators/update-operator-mutator.ts b/packages/instrumenter/src/mutators/update-operator-mutator.ts index ab7abedd9e..269646ad88 100644 --- a/packages/instrumenter/src/mutators/update-operator-mutator.ts +++ b/packages/instrumenter/src/mutators/update-operator-mutator.ts @@ -1,40 +1,50 @@ import babel from '@babel/core'; -import { deepCloneNode } from '../util/index.js'; +import { UpdateOperator } from '@stryker-mutator/api/core'; -import { NodeMutatorConfiguration } from '../mutation-level/mutation-level.js'; +import { deepCloneNode } from '../util/index.js'; import { NodeMutator } from './index.js'; const { types } = babel; -const operators: NodeMutatorConfiguration = { - 'Post++To--': { replacement: '--', mutationName: 'Post++To--' }, - 'Post--To++': { replacement: '++', mutationName: 'Post--To++' }, - 'Pre++To--': { replacement: '--', mutationName: 'Pre++To--' }, - 'Pre--To++': { replacement: '++', mutationName: 'Pre--To++' }, - '++': { replacement: '--', mutationName: '++all' }, - '--': { replacement: '++', mutationName: '--all' }, -}; - -export const updateOperatorMutator: NodeMutator = { +export const updateOperatorMutator: NodeMutator = { name: 'UpdateOperator', + operators: { + PostfixIncrementOperatorNegation: { + replacement: '--', + mutationName: 'PostfixIncrementOperatorNegation', + }, + PostfixDecrementOperatorNegation: { + replacement: '++', + mutationName: 'PostfixDecrementOperatorNegation', + }, + PrefixIncrementOperatorNegation: { + replacement: '--', + mutationName: 'PrefixIncrementOperatorNegation', + }, + PrefixDecrementOperatorNegation: { + replacement: '++', + mutationName: 'PrefixDecrementOperatorNegation', + }, + }, + *mutate(path, levelMutations) { if (path.isUpdateExpression()) { if (levelMutations === undefined) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - yield types.updateExpression(operators[path.node.operator].replacement, deepCloneNode(path.node.argument), path.node.prefix); + const replacement = path.node.operator === '++' ? '--' : '++'; + yield types.updateExpression(replacement, deepCloneNode(path.node.argument), path.node.prefix); } else { let replacement = undefined; if (path.node.prefix && path.node.operator == '++') { - replacement = getReplacement(levelMutations, operators['Pre++To--'].mutationName); + replacement = getReplacement(levelMutations, this.operators.PrefixIncrementOperatorNegation.mutationName); } else if (path.node.prefix && path.node.operator == '--') { - replacement = getReplacement(levelMutations, operators['Pre--To++'].mutationName); + replacement = getReplacement(levelMutations, this.operators.PrefixDecrementOperatorNegation.mutationName); } else if (!path.node.prefix && path.node.operator == '++') { - replacement = getReplacement(levelMutations, operators['Post++To--'].mutationName); + replacement = getReplacement(levelMutations, this.operators.PostfixIncrementOperatorNegation.mutationName); } else if (!path.node.prefix && path.node.operator == '--') { - replacement = getReplacement(levelMutations, operators['Post--To++'].mutationName); + replacement = getReplacement(levelMutations, this.operators.PostfixDecrementOperatorNegation.mutationName); } if (replacement !== undefined) { yield types.updateExpression(replacement, deepCloneNode(path.node.argument), path.node.prefix); @@ -46,7 +56,7 @@ export const updateOperatorMutator: NodeMutator = { function getReplacement(levelMutations: string[], mutationName: string): '--' | '++' | undefined { if (levelMutations.includes(mutationName)) { - const { replacement } = operators[mutationName]; + const { replacement } = updateOperatorMutator.operators[mutationName]; return replacement; } return undefined; diff --git a/packages/instrumenter/src/transformers/babel-transformer.ts b/packages/instrumenter/src/transformers/babel-transformer.ts index 5642d2068b..4e7751d319 100644 --- a/packages/instrumenter/src/transformers/babel-transformer.ts +++ b/packages/instrumenter/src/transformers/babel-transformer.ts @@ -159,7 +159,7 @@ export const transformBabel: AstTransformer = ( //TODO: Create runLevel here const runLevel: MutationLevel | undefined = undefined; for (const defaultLevel of defaultMutationLevels) { - if ('@' + defaultLevel.name in options.includedMutations) { + if (options.includedMutations.includes('@' + defaultLevel.name)) { //For each key in defaultLevel, ADD it to the runLevel } } diff --git a/packages/instrumenter/src/transformers/directive-bookkeeper.ts b/packages/instrumenter/src/transformers/directive-bookkeeper.ts index 6d724058da..15857acd47 100644 --- a/packages/instrumenter/src/transformers/directive-bookkeeper.ts +++ b/packages/instrumenter/src/transformers/directive-bookkeeper.ts @@ -4,6 +4,7 @@ import { notEmpty } from '@stryker-mutator/util'; import { Logger } from '@stryker-mutator/api/logging'; import { NodeMutator } from '../mutators/node-mutator.js'; +import { MutationLevel } from '../mutation-level/mutation-level.js'; const WILDCARD = 'all'; const DEFAULT_REASON = 'Ignored using a comment'; @@ -60,7 +61,7 @@ export class DirectiveBookkeeper { constructor( private readonly logger: Logger, - private readonly allMutators: NodeMutator[], + private readonly allMutators: Array>, private readonly originFileName: string, ) { this.allMutatorNames = this.allMutators.map((x) => x.name.toLowerCase()); diff --git a/packages/instrumenter/test/helpers/expect-mutation.ts b/packages/instrumenter/test/helpers/expect-mutation.ts index 741b51fada..a2a60ee026 100644 --- a/packages/instrumenter/test/helpers/expect-mutation.ts +++ b/packages/instrumenter/test/helpers/expect-mutation.ts @@ -4,6 +4,7 @@ import generator from '@babel/generator'; import { expect } from 'chai'; import { NodeMutator } from '../../src/mutators/node-mutator.js'; +import { MutationLevel } from '../../src/mutation-level/mutation-level.js'; const generate = generator.default; @@ -35,12 +36,12 @@ const plugins = [ 'typescript', ] as ParserPlugin[]; -export function expectJSMutation(sut: NodeMutator, originalCode: string, ...expectedReplacements: string[]): void { +export function expectJSMutation(sut: NodeMutator, originalCode: string, ...expectedReplacements: string[]): void { expectJSMutationWithLevel(sut, undefined, originalCode, ...expectedReplacements); } export function expectJSMutationWithLevel( - sut: NodeMutator, + sut: NodeMutator, level: string[] | undefined, originalCode: string, ...expectedReplacements: string[] diff --git a/packages/instrumenter/test/unit/mutators/arithmatic-operator-mutator.spec.ts b/packages/instrumenter/test/unit/mutators/arithmatic-operator-mutator.spec.ts index 52e8b5f76f..b92ad996f4 100644 --- a/packages/instrumenter/test/unit/mutators/arithmatic-operator-mutator.spec.ts +++ b/packages/instrumenter/test/unit/mutators/arithmatic-operator-mutator.spec.ts @@ -4,7 +4,10 @@ import { arithmeticOperatorMutator as sut } from '../../../src/mutators/arithmet import { expectJSMutation, expectJSMutationWithLevel } from '../../helpers/expect-mutation.js'; import { MutationLevel } from '../../../src/mutation-level/mutation-level.js'; -const arithmeticLevel: MutationLevel = { name: 'ArithemticLevel', ArithmeticOperator: ['+To-', '-To+', '*To/'] }; +const arithmeticLevel: MutationLevel = { + name: 'ArithemticLevel', + ArithmeticOperator: ['AdditionOperatorNegation', 'SubtractionOperatorNegation', 'MultiplicationOperatorNegation'], +}; describe(sut.name, () => { it('should have name "ArithmeticOperator"', () => { diff --git a/packages/instrumenter/test/unit/mutators/array-declaration-mutator.spec.ts b/packages/instrumenter/test/unit/mutators/array-declaration-mutator.spec.ts index 33a2df8226..2c6f266308 100644 --- a/packages/instrumenter/test/unit/mutators/array-declaration-mutator.spec.ts +++ b/packages/instrumenter/test/unit/mutators/array-declaration-mutator.spec.ts @@ -2,8 +2,12 @@ import { expect } from 'chai'; import { arrayDeclarationMutator as sut } from '../../../src/mutators/array-declaration-mutator.js'; import { expectJSMutation, expectJSMutationWithLevel } from '../../helpers/expect-mutation.js'; +import { MutationLevel } from '../../../src/mutation-level/mutation-level.js'; -const arrayDeclarationLevel: string[] = ['EmptyArray', 'EmptyArrayConstructor', 'FilledArray', 'FilledArrayConstructor']; +const arrayDeclarationLevel: MutationLevel = { + name: 'ArrayDeclarationLevel', + ArrayDeclaration: ['ArrayLiteralItemsFill', 'ArrayConstructorItemsRemoval', 'ArrayLiteralItemsRemoval', 'ArrayConstructorItemsFill'], +}; describe(sut.name, () => { it('should have name "ArrayDeclaration"', () => { @@ -44,7 +48,7 @@ describe(sut.name, () => { it('should only mutate [], new Array(), new Array(x,y) and [x,y] from all possible mutators', () => { expectJSMutationWithLevel( sut, - arrayDeclarationLevel, + arrayDeclarationLevel.ArrayDeclaration, '[]; new Array(); new Array({x:"", y:""}); [{x:"", y:""}]', '["Stryker was here"]; new Array(); new Array({x:"", y:""}); [{x:"", y:""}]', // mutates [] '[]; new Array("Stryker was here"); new Array({x:"", y:""}); [{x:"", y:""}]', // mutates new Array() diff --git a/packages/instrumenter/test/unit/mutators/arrow-function-mutator.spec.ts b/packages/instrumenter/test/unit/mutators/arrow-function-mutator.spec.ts index 152b207fba..6b58ae91f8 100644 --- a/packages/instrumenter/test/unit/mutators/arrow-function-mutator.spec.ts +++ b/packages/instrumenter/test/unit/mutators/arrow-function-mutator.spec.ts @@ -4,7 +4,7 @@ import { arrowFunctionMutator as sut } from '../../../src/mutators/arrow-functio import { expectJSMutation, expectJSMutationWithLevel } from '../../helpers/expect-mutation.js'; import { MutationLevel } from '../../../src/mutation-level/mutation-level.js'; -const arrowFunctionLevel: MutationLevel = { name: 'ArrowFunctionLevel', ArrowFunction: ['ArrowFunction'] }; +const arrowFunctionLevel: MutationLevel = { name: 'ArrowFunctionLevel', ArrowFunction: ['ArrowFunctionRemoval'] }; const arrowFunctionUndefinedLevel: MutationLevel = { name: 'ArrowFunctionLevel' }; describe(sut.name, () => { diff --git a/packages/instrumenter/test/unit/mutators/assignment-operator-mutator.spec.ts b/packages/instrumenter/test/unit/mutators/assignment-operator-mutator.spec.ts index 45f137ebb7..010ccb9a67 100644 --- a/packages/instrumenter/test/unit/mutators/assignment-operator-mutator.spec.ts +++ b/packages/instrumenter/test/unit/mutators/assignment-operator-mutator.spec.ts @@ -4,22 +4,25 @@ import { assignmentOperatorMutator as sut } from '../../../src/mutators/assignme import { expectJSMutation, expectJSMutationWithLevel } from '../../helpers/expect-mutation.js'; import { MutationLevel } from '../../../src/mutation-level/mutation-level.js'; -const assignmentOperatorLevel: MutationLevel = { name: 'AssignmentOperatorLevel', AssignmentOperator: ['-=To+=', '<<=To>>=', '&&=To||='] }; +const assignmentOperatorLevel: MutationLevel = { + name: 'AssignmentOperatorLevel', + AssignmentOperator: ['SubtractionAssignmentNegation', 'LeftShiftAssignmentNegation', 'LogicalAndAssignmentNegation'], +}; const assignmentOperatorAllLevel: MutationLevel = { name: 'AssignmentOperatorLevel', AssignmentOperator: [ - '+=To-=', - '-=To+=', - '*=To/=', - '/=To*=', - '%=To*=', - '<<=To>>=', - '>>=To<<=', - '&=To|=', - '|=To&=', - '&&=To||=', - '||=To&&=', - '??=To&&=', + 'AdditionAssignmentNegation', + 'SubtractionAssignmentNegation', + 'MultiplicationAssignmentNegation', + 'DivisionAssignmentNegation', + 'RemainderAssignmentToMultiplicationReplacement', + 'LeftShiftAssignmentNegation', + 'RightShiftAssignmentNegation', + 'BitwiseAndAssignmentNegation', + 'BitwiseOrAssignmentNegation', + 'LogicalAndAssignmentNegation', + 'LogicalOrAssignmentNegation', + 'NullishCoalescingAssignmentToLogicalAndReplacement', ], }; const assignmentOperatorUndefinedLevel: MutationLevel = { name: 'AssignmentOperatorLevel' }; diff --git a/packages/instrumenter/test/unit/mutators/block-statement-mutator.spec.ts b/packages/instrumenter/test/unit/mutators/block-statement-mutator.spec.ts index e986433eb3..f83727b2e6 100644 --- a/packages/instrumenter/test/unit/mutators/block-statement-mutator.spec.ts +++ b/packages/instrumenter/test/unit/mutators/block-statement-mutator.spec.ts @@ -4,7 +4,7 @@ import { blockStatementMutator as sut } from '../../../src/mutators/block-statem import { expectJSMutation, expectJSMutationWithLevel } from '../../helpers/expect-mutation.js'; import { MutationLevel } from '../../../src/mutation-level/mutation-level.js'; -const blockStatementLevel: MutationLevel = { name: 'BlockStatementLevel', BlockStatement: ['BlockStatement'] }; +const blockStatementLevel: MutationLevel = { name: 'BlockStatementLevel', BlockStatement: ['BlockStatementRemoval'] }; const blockStatementUndefinedLevel: MutationLevel = { name: 'BlockStatementLevel' }; describe(sut.name, () => { diff --git a/packages/instrumenter/test/unit/mutators/boolean-literal-mutator.spec.ts b/packages/instrumenter/test/unit/mutators/boolean-literal-mutator.spec.ts index d6504b28c5..e76810657b 100644 --- a/packages/instrumenter/test/unit/mutators/boolean-literal-mutator.spec.ts +++ b/packages/instrumenter/test/unit/mutators/boolean-literal-mutator.spec.ts @@ -6,12 +6,12 @@ import { MutationLevel } from '../../../src/mutation-level/mutation-level.js'; const booleanLiteralLevel: MutationLevel = { name: 'BooleanLiteralLevel', - BooleanLiteral: ['TrueToFalse', 'RemoveNegation'], + BooleanLiteral: ['TrueLiteralNegation', 'LogicalNotRemoval'], }; const booleanLiteralAllLevel: MutationLevel = { name: 'BooleanLiteralLevel', - BooleanLiteral: ['TrueToFalse', 'FalseToTrue', 'RemoveNegation'], + BooleanLiteral: ['TrueLiteralNegation', 'FalseLiteralNegation', 'LogicalNotRemoval'], }; const booleanLiteralUndefinedLevel: MutationLevel = { diff --git a/packages/instrumenter/test/unit/mutators/conditional-expression-mutator.spec.ts b/packages/instrumenter/test/unit/mutators/conditional-expression-mutator.spec.ts index edc7a7e6fe..349d6114ad 100644 --- a/packages/instrumenter/test/unit/mutators/conditional-expression-mutator.spec.ts +++ b/packages/instrumenter/test/unit/mutators/conditional-expression-mutator.spec.ts @@ -2,10 +2,31 @@ import { expect } from 'chai'; import { expectJSMutation, expectJSMutationWithLevel } from '../../helpers/expect-mutation.js'; import { conditionalExpressionMutator as sut } from '../../../src/mutators/conditional-expression-mutator.js'; - -const conditionLevel: string[] = ['ForLoopToFalse', 'IfToFalse', 'IfToTrue', 'SwitchToEmpty']; -const conditionLevel2: string[] = ['WhileLoopToFalse', 'BooleanExpressionToFalse', 'DoWhileLoopToFalse', 'BooleanExpressionToTrue']; -const conditionLevel3 = undefined; +import { MutationLevel } from '../../../src/mutation-level/mutation-level.js'; + +const conditionalLevel: MutationLevel = { + name: 'ConditionalLevel', + ConditionalExpression: [ + 'ForLoopConditionToFalseReplacement', + 'IfConditionToFalseReplacement', + 'IfConditionToTrueReplacement', + 'SwitchStatementBodyRemoval', + ], +}; + +const conditionalLevel2: MutationLevel = { + name: 'ConditionalLevel2', + ConditionalExpression: [ + 'WhileLoopConditionToFalseReplacement', + 'BooleanExpressionToFalseReplacement', + 'DoWhileLoopConditionToFalseReplacement', + 'BooleanExpressionToTrueReplacement', + ], +}; + +const conditionalLevelUndefined: MutationLevel = { + name: 'ConditionLevelEmpty', +}; describe(sut.name, () => { it('should have name "ConditionalExpression"', () => { @@ -148,7 +169,7 @@ describe(sut.name, () => { it('should only mutate for, if and switch statement', () => { expectJSMutationWithLevel( sut, - conditionLevel, + conditionalLevel.ConditionalExpression, 'for (var i = 0; i < 10; i++) { };if(x > 2); switch (x) {case 0: 2}', 'for (var i = 0; false; i++) { };if(x > 2); switch (x) {case 0: 2}', // mutates for loop 'for (var i = 0; i < 10; i++) { };if(false); switch (x) {case 0: 2}', // mutates if statement to false @@ -160,7 +181,7 @@ describe(sut.name, () => { it('should only mutate while, while do and boolean expression', () => { expectJSMutationWithLevel( sut, - conditionLevel2, + conditionalLevel2.ConditionalExpression, 'while (a > b) { }; do { } while (a > b); var x = a > b ? 1 : 2', 'while (false) { }; do { } while (a > b); var x = a > b ? 1 : 2', // mutates while loop 'while (a > b) { }; do { } while (a > b); var x = false ? 1 : 2', // mutates boolean to false @@ -172,7 +193,7 @@ describe(sut.name, () => { it('should only mutate all', () => { expectJSMutationWithLevel( sut, - conditionLevel3, + conditionalLevelUndefined.ConditionalExpression, 'for (var i = 0; i < 10; i++) { };if(x > 2); switch (x) {case 0: 2}; while (a > b); { } do { } while (a > b); var x = a > b ? 1 : 2', 'for (var i = 0; false; i++) { };if(x > 2); switch (x) {case 0: 2}; while (a > b); { } do { } while (a > b); var x = a > b ? 1 : 2', // mutates for loop 'for (var i = 0; i < 10; i++) { };if(false); switch (x) {case 0: 2}; while (a > b); { } do { } while (a > b); var x = a > b ? 1 : 2', // mutates if statement to false diff --git a/packages/instrumenter/test/unit/mutators/equality-operator-mutator.spec.ts b/packages/instrumenter/test/unit/mutators/equality-operator-mutator.spec.ts index 577e737e36..efe7bfcb73 100644 --- a/packages/instrumenter/test/unit/mutators/equality-operator-mutator.spec.ts +++ b/packages/instrumenter/test/unit/mutators/equality-operator-mutator.spec.ts @@ -4,9 +4,21 @@ import { equalityOperatorMutator as sut } from '../../../src/mutators/equality-o import { expectJSMutation, expectJSMutationWithLevel } from '../../helpers/expect-mutation.js'; import { MutationLevel } from '../../../src/mutation-level/mutation-level.js'; -const equalityLevelA: MutationLevel = { name: 'EqualityLevelA', EqualityOperator: ['=', '>=To>', '>=To<', '==To!='] }; +const equalityLevelA: MutationLevel = { + name: 'EqualityLevelA', + EqualityOperator: [ + 'LessThanOperatorBoundary', + 'LessThanOperatorNegation', + 'GreaterThanEqualOperatorBoundary', + 'GreaterThanEqualOperatorNegation', + 'EqualityOperatorNegation', + ], +}; -const equalityLevelB: MutationLevel = { name: 'EqualityLevelB', EqualityOperator: ['<=To>', '>To<=', '===To!=='] }; +const equalityLevelB: MutationLevel = { + name: 'EqualityLevelB', + EqualityOperator: ['LessThanEqualOperatorNegation', 'GreaterThanOperatorNegation', 'StrictEqualityOperatorNegation'], +}; describe(sut.name, () => { it('should have name "EqualityOperator"', () => { diff --git a/packages/instrumenter/test/unit/mutators/logical-operator-mutator.spec.ts b/packages/instrumenter/test/unit/mutators/logical-operator-mutator.spec.ts index 7afc4fa073..acf7279b3d 100644 --- a/packages/instrumenter/test/unit/mutators/logical-operator-mutator.spec.ts +++ b/packages/instrumenter/test/unit/mutators/logical-operator-mutator.spec.ts @@ -2,6 +2,12 @@ import { expect } from 'chai'; import { logicalOperatorMutator as sut } from '../../../src/mutators/logical-operator-mutator.js'; import { expectJSMutation, expectJSMutationWithLevel } from '../../helpers/expect-mutation.js'; +import { MutationLevel } from '../../../src/mutation-level/mutation-level.js'; + +const logicalOpLevel: MutationLevel = { + name: 'EqualityLevelB', + LogicalOperator: ['LogicalOrOperatorNegation', 'LogicalAndOperatorNegation'], +}; describe(sut.name, () => { it('should have name "LogicalOperator"', () => { @@ -26,8 +32,7 @@ describe(sut.name, () => { }); it('should only mutate || and &&', () => { - const level = ['||To&&', '&&To||']; - expectJSMutationWithLevel(sut, level, 'a || b; a && b; a ?? b', 'a && b; a && b; a ?? b', 'a || b; a || b; a ?? b'); + expectJSMutationWithLevel(sut, logicalOpLevel.LogicalOperator, 'a || b; a && b; a ?? b', 'a && b; a && b; a ?? b', 'a || b; a || b; a ?? b'); }); it('should mutate all three', () => { diff --git a/packages/instrumenter/test/unit/mutators/method-expression-mutator.spec.ts b/packages/instrumenter/test/unit/mutators/method-expression-mutator.spec.ts index a74a27268a..d6b8cf83a0 100644 --- a/packages/instrumenter/test/unit/mutators/method-expression-mutator.spec.ts +++ b/packages/instrumenter/test/unit/mutators/method-expression-mutator.spec.ts @@ -2,6 +2,12 @@ import { expect } from 'chai'; import { methodExpressionMutator as sut } from '../../../src/mutators/method-expression-mutator.js'; import { expectJSMutation, expectJSMutationWithLevel } from '../../helpers/expect-mutation.js'; +import { MutationLevel } from '../../../src/mutation-level/mutation-level.js'; + +const methodExpressionLevel: MutationLevel = { + name: 'methodExpressionLevel', + MethodExpression: ['EndsWithMethodCallNegation', 'StartsWithMethodCallNegation', 'SubstringMethodCallRemoval', 'ToLowerCaseMethodCallNegation'], +}; describe(sut.name, () => { it('should have name "MethodExpression"', () => { @@ -149,10 +155,6 @@ describe(sut.name, () => { }); it('should only mutate methods that are allowed by a MutationLevel and ignore others', () => { - const methodExpressionLevel = { - name: 'methodExpressionLevel', - MethodExpression: ['endsWithToStartsWith', 'startsWithToEndsWith', 'removeSubstring', 'toLowerCaseToToUpperCase'], - }; // The below should be swapped expectJSMutationWithLevel(sut, methodExpressionLevel.MethodExpression, 'text.startsWith();', 'text.endsWith();'); expectJSMutationWithLevel(sut, methodExpressionLevel.MethodExpression, 'text.endsWith();', 'text.startsWith();'); diff --git a/packages/instrumenter/test/unit/mutators/mutate.spec.ts b/packages/instrumenter/test/unit/mutators/mutate.spec.ts index 2d58fb9cf6..0a507e77af 100644 --- a/packages/instrumenter/test/unit/mutators/mutate.spec.ts +++ b/packages/instrumenter/test/unit/mutators/mutate.spec.ts @@ -5,6 +5,7 @@ import { fileURLToPath, pathToFileURL } from 'url'; import { expect } from 'chai'; import { allMutators, NodeMutator } from '../../../src/mutators/index.js'; +import { MutationLevel } from '../../../src/mutation-level/mutation-level.js'; describe('allMutators', () => { it('should include all mutators', async () => { @@ -23,7 +24,7 @@ describe('allMutators', () => { } return mutatorModule[keys[0]]; }), - )) as NodeMutator[]; + )) as Array>; actualMutators.forEach((mutator) => { expect(allMutators.includes(mutator), `${mutator.name} is missing!`).ok; }); diff --git a/packages/instrumenter/test/unit/mutators/object-literal-mutator.spec.ts b/packages/instrumenter/test/unit/mutators/object-literal-mutator.spec.ts index 06c645bd98..d727ffc9c8 100644 --- a/packages/instrumenter/test/unit/mutators/object-literal-mutator.spec.ts +++ b/packages/instrumenter/test/unit/mutators/object-literal-mutator.spec.ts @@ -4,7 +4,7 @@ import { objectLiteralMutator as sut } from '../../../src/mutators/object-litera import { expectJSMutation, expectJSMutationWithLevel } from '../../helpers/expect-mutation.js'; import { MutationLevel } from '../../../src/mutation-level/mutation-level.js'; -const objectLiteralLevel: MutationLevel = { name: 'ObjectLiteralLevel', ObjectLiteral: ['ObjectLiteral'] }; +const objectLiteralLevel: MutationLevel = { name: 'ObjectLiteralLevel', ObjectLiteral: ['ObjectLiteralPropertiesRemoval'] }; const objectLiteralUndefinedLevel: MutationLevel = { name: 'ObjectLiteralLevel' }; describe(sut.name, () => { diff --git a/packages/instrumenter/test/unit/mutators/optional-chaining-mutator.spec.ts b/packages/instrumenter/test/unit/mutators/optional-chaining-mutator.spec.ts index 541ff6fda8..9e887c5609 100644 --- a/packages/instrumenter/test/unit/mutators/optional-chaining-mutator.spec.ts +++ b/packages/instrumenter/test/unit/mutators/optional-chaining-mutator.spec.ts @@ -3,6 +3,12 @@ import { expect } from 'chai'; import { optionalChainingMutator as sut } from '../../../src/mutators/optional-chaining-mutator.js'; import { expectJSMutation, expectJSMutationWithLevel } from '../../helpers/expect-mutation.js'; +import { MutationLevel } from '../../../src/mutation-level/mutation-level.js'; + +const optionalChainingLevel: MutationLevel = { + name: 'OptionalChainingLevel', + OptionalChaining: ['OptionalMemberExpressionOptionalRemoval'], +}; describe(sut.name, () => { it('should have name "OptionalChaining"', () => { @@ -34,10 +40,9 @@ describe(sut.name, () => { it('should only mutate OptionalMemberExpression from all possible mutators', () => { expectJSMutationWithLevel( sut, - ['OptionalMemberExpression'], + optionalChainingLevel.OptionalChaining, 'foo?.bar; foo?.[0]; foo?.()', 'foo.bar; foo?.[0]; foo?.()', // removes .bar optional - 'foo?.bar; foo[0]; foo?.()', // removes [0] optional ); }); it('should block all mutators', () => { diff --git a/packages/instrumenter/test/unit/mutators/regex-mutator.spec.ts b/packages/instrumenter/test/unit/mutators/regex-mutator.spec.ts index d0d74c0846..b035bf9f47 100644 --- a/packages/instrumenter/test/unit/mutators/regex-mutator.spec.ts +++ b/packages/instrumenter/test/unit/mutators/regex-mutator.spec.ts @@ -5,7 +5,7 @@ import { regexMutator as sut } from '../../../src/mutators/regex-mutator.js'; import { expectJSMutation, expectJSMutationWithLevel } from '../../helpers/expect-mutation.js'; import { MutationLevel } from '../../../src/mutation-level/mutation-level.js'; -const regexLevel: MutationLevel = { name: 'RegexLevel', Regex: ['Regex'] }; +const regexLevel: MutationLevel = { name: 'RegexLevel', Regex: ['RegexRemoval'] }; const regexUndefinedLevel: MutationLevel = { name: 'RegexLevel' }; describe(sut.name, () => { diff --git a/packages/instrumenter/test/unit/mutators/string-literal-mutator.spec.ts b/packages/instrumenter/test/unit/mutators/string-literal-mutator.spec.ts index af720fc834..f1a248077d 100644 --- a/packages/instrumenter/test/unit/mutators/string-literal-mutator.spec.ts +++ b/packages/instrumenter/test/unit/mutators/string-literal-mutator.spec.ts @@ -2,6 +2,12 @@ import { expect } from 'chai'; import { expectJSMutation, expectJSMutationWithLevel } from '../../helpers/expect-mutation.js'; import { stringLiteralMutator as sut } from '../../../src/mutators/string-literal-mutator.js'; +import { MutationLevel } from '../../../src/mutation-level/mutation-level.js'; + +const stringLiteralLevel: MutationLevel = { + name: 'ObjectLiteralLevel', + StringLiteral: ['FilledStringLiteralToEmptyReplacement', 'FilledInterpolatedStringToEmptyReplacement'], +}; describe(sut.name, () => { it('should have name "StringLiteral"', () => { @@ -117,7 +123,7 @@ describe(sut.name, () => { it('should only mutate EmptyString and EmptyInterpolation from all possible mutations', () => { expectJSMutationWithLevel( sut, - ['EmptyString', 'EmptyInterpolation'], + stringLiteralLevel.StringLiteral, 'const bar = "bar"; const foo = `name: ${level_name}`; const emptyString=""; const emptyInterp=``', 'const bar = ""; const foo = `name: ${level_name}`; const emptyString=""; const emptyInterp=``', // empties string 'const bar = "bar"; const foo = ``; const emptyString=""; const emptyInterp=``', // empties interpolation diff --git a/packages/instrumenter/test/unit/mutators/unary-operator-mutator.spec.ts b/packages/instrumenter/test/unit/mutators/unary-operator-mutator.spec.ts index 6d40318b6a..b4db64192b 100644 --- a/packages/instrumenter/test/unit/mutators/unary-operator-mutator.spec.ts +++ b/packages/instrumenter/test/unit/mutators/unary-operator-mutator.spec.ts @@ -2,6 +2,13 @@ import { expect } from 'chai'; import { unaryOperatorMutator as sut } from '../../../src/mutators/unary-operator-mutator.js'; import { expectJSMutation, expectJSMutationWithLevel } from '../../helpers/expect-mutation.js'; +import { MutationLevel } from '../../../src/mutation-level/mutation-level.js'; + +const unaryOperatorLevelA: MutationLevel = { + name: 'unaryOperatorA', + UnaryOperator: ['UnaryPlusOperatorNegation', 'UnaryBitwiseOrRemoval'], +}; +const unaryOperatorLevelB: MutationLevel = { name: 'unaryOperatorB', UnaryOperator: ['UnaryMinOperatorNegation'] }; describe(sut.name, () => { it('should have name "UnaryOperator"', () => { @@ -29,12 +36,10 @@ describe(sut.name, () => { }); it('should not mutate -b to +b', () => { - const unaryOperatorLevelA = { name: 'unaryOperatorA', UnaryOperator: ['+To-', 'remove~'] }; expectJSMutationWithLevel(sut, unaryOperatorLevelA.UnaryOperator, '+a; -b; ~c;', '-a; -b; ~c;', '+a; -b; c;'); }); it('should only mutate -b to +b', () => { - const unaryOperatorLevelB = { name: 'unaryOperatorB', UnaryOperator: ['-To+'] }; expectJSMutationWithLevel(sut, unaryOperatorLevelB.UnaryOperator, '+a; -b; ~c;', '+a; +b; ~c;'); }); }); diff --git a/packages/instrumenter/test/unit/mutators/update-operator-mutator.spec.ts b/packages/instrumenter/test/unit/mutators/update-operator-mutator.spec.ts index 1269d25c46..5b02fbd78e 100644 --- a/packages/instrumenter/test/unit/mutators/update-operator-mutator.spec.ts +++ b/packages/instrumenter/test/unit/mutators/update-operator-mutator.spec.ts @@ -2,10 +2,21 @@ import { expect } from 'chai'; import { updateOperatorMutator as sut } from '../../../src/mutators/update-operator-mutator.js'; import { expectJSMutation, expectJSMutationWithLevel } from '../../helpers/expect-mutation.js'; +import { MutationLevel } from '../../../src/mutation-level/mutation-level.js'; -const updateLevel: string[] = ['Pre--To++', 'Pre++To--']; -const updateLevel2: string[] = ['Post++To--', 'Post--To++']; -const updateLevel3 = undefined; +const updateLevel: MutationLevel = { + name: 'UpdateLevel', + UpdateOperator: ['PrefixDecrementOperatorNegation', 'PrefixIncrementOperatorNegation'], +}; + +const updateLevel2: MutationLevel = { + name: 'UpdateLevel2', + UpdateOperator: ['PostfixDecrementOperatorNegation', 'PostfixIncrementOperatorNegation'], +}; + +const updateUndefinedLevel: MutationLevel = { + name: 'UpdateLevel3', +}; describe(sut.name, () => { it('should have name "UpdateOperator"', () => { @@ -31,7 +42,7 @@ describe(sut.name, () => { it('should only mutate --a and ++a', () => { expectJSMutationWithLevel( sut, - updateLevel, + updateLevel.UpdateOperator, '--a; ++a; a--; a++', '++a; ++a; a--; a++', //mutates --a '--a; --a; a--; a++', //mutates ++a @@ -41,7 +52,7 @@ describe(sut.name, () => { it('should only mutate a-- and a++', () => { expectJSMutationWithLevel( sut, - updateLevel2, + updateLevel2.UpdateOperator, '--a; ++a; a--; a++', '--a; ++a; a--; a--', //mutates a++ '--a; ++a; a++; a++', //mutates a-- @@ -51,7 +62,7 @@ describe(sut.name, () => { it('should mutate all', () => { expectJSMutationWithLevel( sut, - updateLevel3, + updateUndefinedLevel.UpdateOperator, '--a; ++a; a--; a++', '++a; ++a; a--; a++', //mutates --a '--a; --a; a--; a++', //mutates ++a diff --git a/packages/instrumenter/test/unit/transformers/babel-transformer.spec.ts b/packages/instrumenter/test/unit/transformers/babel-transformer.spec.ts index 9ca93afe64..a166951e6f 100644 --- a/packages/instrumenter/test/unit/transformers/babel-transformer.spec.ts +++ b/packages/instrumenter/test/unit/transformers/babel-transformer.spec.ts @@ -14,6 +14,7 @@ import { instrumentationBabelHeader } from '../../../src/util/index.js'; import { MutantPlacer } from '../../../src/mutant-placers/index.js'; import { NodeMutator } from '../../../src/mutators/index.js'; import { createJSAst, createTSAst } from '../../helpers/factories.js'; +import { MutationLevel } from '../../../src/mutation-level/mutation-level.js'; const generate = generator.default; const { types } = babel; @@ -26,20 +27,22 @@ const { types } = babel; */ describe('babel-transformer', () => { let context: sinon.SinonStubbedInstance; - let mutators: NodeMutator[]; + let mutators: Array>; let mutantPlacers: MutantPlacer[]; let mutantCollector: MutantCollector; - const fooMutator: NodeMutator = { + const fooMutator: NodeMutator = { name: 'Foo', + operators: {}, *mutate(path) { if (path.isIdentifier() && path.node.name === 'foo') { yield types.identifier('bar'); } }, }; - const plusMutator: NodeMutator = { + const plusMutator: NodeMutator = { name: 'Plus', + operators: {}, *mutate(path) { if (path.isBinaryExpression() && path.node.operator === '+') { yield types.binaryExpression('-', types.cloneNode(path.node.left, true), types.cloneNode(path.node.right, true)); @@ -622,6 +625,7 @@ describe('babel-transformer', () => { }); mutators.push({ name: 'blockMutatorForTest', + operators: {}, *mutate(path) { if (path.isBlockStatement()) { yield types.blockStatement([]); @@ -729,12 +733,10 @@ describe('babel-transformer', () => { }); function act(ast: ScriptAst) { - (transformBabel as (...args: [...Parameters>, mutators: NodeMutator[], mutantPlacers: MutantPlacer[]]) => void)( - ast, - mutantCollector, - context, - mutators, - mutantPlacers, - ); + ( + transformBabel as ( + ...args: [...Parameters>, mutators: Array>, mutantPlacers: MutantPlacer[]] + ) => void + )(ast, mutantCollector, context, mutators, mutantPlacers); } });