diff --git a/packages/instrumenter/src/mutators/boolean-literal-mutator.ts b/packages/instrumenter/src/mutators/boolean-literal-mutator.ts index c339c99b3d..97f1cd7977 100644 --- a/packages/instrumenter/src/mutators/boolean-literal-mutator.ts +++ b/packages/instrumenter/src/mutators/boolean-literal-mutator.ts @@ -6,15 +6,41 @@ const { types } = babel; import { NodeMutator } from './index.js'; +const booleanLiteralReplacements = Object.freeze({ + // prettier-ignore + 'true': {replacement: 'false', mutatorName: 'TrueToFalse'}, + // prettier-ignore + 'false': {replacement: 'true', mutatorName: 'FalseToTrue'}, + '!': { replacement: '', mutatorName: 'RemoveNegation' }, +} as const); + export const booleanLiteralMutator: NodeMutator = { name: 'BooleanLiteral', - *mutate(path) { - if (path.isBooleanLiteral()) { - yield types.booleanLiteral(!path.node.value); - } - if (path.isUnaryExpression() && path.node.operator === '!' && path.node.prefix) { - yield deepCloneNode(path.node.argument); + *mutate(path, options: string[] | undefined) { + if (isInMutationLevel(path, options)) { + if (path.isBooleanLiteral()) { + yield types.booleanLiteral(!path.node.value); + } + if (path.isUnaryExpression() && path.node.operator === '!' && path.node.prefix) { + yield deepCloneNode(path.node.argument); + } } }, }; + +function isInMutationLevel(path: any, mutators: string[] | undefined): boolean { + if (mutators === undefined) { + return true; + } + if (path.isBooleanLiteral()) { + const { mutatorName } = booleanLiteralReplacements[path.node.value as keyof typeof booleanLiteralReplacements]; + return mutators.some((lit) => lit === mutatorName); + } + return ( + path.isUnaryExpression() && + path.node.operator === '!' && + path.node.prefix && + mutators.some((lit: string) => lit === booleanLiteralReplacements['!'].mutatorName) + ); +} 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 1cf171553e..23ee6ee178 100644 --- a/packages/instrumenter/test/unit/mutators/boolean-literal-mutator.spec.ts +++ b/packages/instrumenter/test/unit/mutators/boolean-literal-mutator.spec.ts @@ -1,7 +1,23 @@ import { expect } from 'chai'; +import { MutationLevel } from '@stryker-mutator/api/core'; + import { booleanLiteralMutator as sut } from '../../../src/mutators/boolean-literal-mutator.js'; -import { expectJSMutation } from '../../helpers/expect-mutation.js'; +import { expectJSMutation, expectJSMutationWithLevel } from '../../helpers/expect-mutation.js'; + +const booleanLiteralLevel: MutationLevel = { + name: 'BooleanLiteralLevel', + BooleanLiteral: ['TrueToFalse', 'RemoveNegation'], +}; + +const booleanLiteralAllLevel: MutationLevel = { + name: 'BooleanLiteralLevel', + BooleanLiteral: ['TrueToFalse', 'FalseToTrue', 'RemoveNegation'], +}; + +const booleanLiteralUndefinedLevel: MutationLevel = { + name: 'BooleanLiteralLevel', +}; describe(sut.name, () => { it('should have name "BooleanLiteral"', () => { @@ -19,4 +35,40 @@ describe(sut.name, () => { it('should mutate !a to a', () => { expectJSMutation(sut, '!a', 'a'); }); + + it('should only mutate what is defined in the mutation level', () => { + expectJSMutationWithLevel( + sut, + booleanLiteralLevel.BooleanLiteral, + 'if (true) {}; if (false) {}; if (!value) {}', + 'if (false) {}; if (false) {}; if (!value) {}', + 'if (true) {}; if (false) {}; if (value) {}', + ); + }); + + it('should not mutate anything if there are no values in the mutation level', () => { + expectJSMutationWithLevel(sut, [], 'if (true) {}; if (false) {}; if (!value) {}'); + }); + + it('should mutate everything if everything is in the mutation level', () => { + expectJSMutationWithLevel( + sut, + booleanLiteralAllLevel.BooleanLiteral, + 'if (true) {}; if (false) {}; if (!value) {}', + 'if (false) {}; if (false) {}; if (!value) {}', + 'if (true) {}; if (false) {}; if (value) {}', + 'if (true) {}; if (true) {}; if (!value) {}', + ); + }); + + it('should mutate everything if the mutation level is undefined', () => { + expectJSMutationWithLevel( + sut, + booleanLiteralUndefinedLevel.BooleanLiteral, + 'if (true) {}; if (false) {}; if (!value) {}', + 'if (false) {}; if (false) {}; if (!value) {}', + 'if (true) {}; if (false) {}; if (value) {}', + 'if (true) {}; if (true) {}; if (!value) {}', + ); + }); }); diff --git a/testing-project/stryker.conf.json b/testing-project/stryker.conf.json index c955301f07..38496be1d7 100644 --- a/testing-project/stryker.conf.json +++ b/testing-project/stryker.conf.json @@ -19,7 +19,8 @@ { "name": "default", "ArithmeticOperator": ["+To-", "-To+", "*To/"], - "ArrayDeclaration": ["EmptyArray", "FilledArray", "FilledArrayConstructor"] + "ArrayDeclaration": ["EmptyArray", "FilledArray", "FilledArrayConstructor"], + "BooleanLiteral": ["TrueToFalse", "RemoveNegation"] }, {