diff --git a/packages/api/schema/stryker-core.json b/packages/api/schema/stryker-core.json index 72f0b358cc..8b241b13d5 100644 --- a/packages/api/schema/stryker-core.json +++ b/packages/api/schema/stryker-core.json @@ -754,9 +754,14 @@ "description": "Replace ```\"\"``` with ```\"Stryker was here!\"```." }, { - "const": "Interpolation", - "title": "InterpolationMutator", + "const": "EmptyInterpolation", + "title": "EmptyInterpolation", "description": "Replace ```s\"foo ${bar}\"``` with ```s\"\"```." + }, + { + "const": "FillInterpolation", + "title": "FillInterpolation", + "description": "Replace ```s\"\"``` with ```s\"Stryker was here!\"```." } ] } diff --git a/packages/instrumenter/src/mutators/mutation-level-options.ts b/packages/instrumenter/src/mutators/mutation-level-options.ts index 8da2c93238..09dfaf59c2 100644 --- a/packages/instrumenter/src/mutators/mutation-level-options.ts +++ b/packages/instrumenter/src/mutators/mutation-level-options.ts @@ -3,3 +3,4 @@ import { MutationLevel } from '@stryker-mutator/api/core'; export interface RunLevelOptions { runLevel?: MutationLevel; } +export type MutationOperator = Record; diff --git a/packages/instrumenter/src/mutators/string-literal-mutator.ts b/packages/instrumenter/src/mutators/string-literal-mutator.ts index 9b3dc4c03f..2b66792a1d 100644 --- a/packages/instrumenter/src/mutators/string-literal-mutator.ts +++ b/packages/instrumenter/src/mutators/string-literal-mutator.ts @@ -1,19 +1,43 @@ import babel, { type NodePath } from '@babel/core'; import { NodeMutator } from './node-mutator.js'; +import { MutationOperator } from './mutation-level-options.js'; const { types } = babel; +const operators: MutationOperator = { + FillString: { replacementOperator: types.stringLiteral('Stryker was here!'), mutatorName: 'FillString' }, + EmptyString: { replacementOperator: types.stringLiteral(''), mutatorName: 'EmptyString' }, + EmptyInterpolation: { replacementOperator: types.templateLiteral([types.templateElement({ raw: '' })], []), mutatorName: 'EmptyInterpolation' }, + FillInterpolation: { + replacementOperator: types.templateLiteral([types.templateElement({ raw: 'Stryker was here!' })], []), + mutatorName: 'FillInterpolation', + }, +}; + export const stringLiteralMutator: NodeMutator = { name: 'StringLiteral', - *mutate(path) { + *mutate(path, operations: string[] | undefined) { if (path.isTemplateLiteral()) { - const replacement = path.node.quasis.length === 1 && path.node.quasis[0].value.raw.length === 0 ? 'Stryker was here!' : ''; - yield types.templateLiteral([types.templateElement({ raw: replacement })], []); + const stringIsEmpty = path.node.quasis.length === 1 && path.node.quasis[0].value.raw.length === 0; + if ( + operations === undefined || + (stringIsEmpty && operations.includes(operators.FillInterpolation.mutatorName)) || + (!stringIsEmpty && operations.includes(operators.EmptyInterpolation.mutatorName)) + ) { + yield stringIsEmpty ? operators.FillInterpolation.replacementOperator : operators.EmptyInterpolation.replacementOperator; + } } if (path.isStringLiteral() && isValidParent(path)) { - yield types.stringLiteral(path.node.value.length === 0 ? 'Stryker was here!' : ''); + const stringIsEmpty = path.node.value.length === 0; + if ( + operations === undefined || + (stringIsEmpty && operations.includes(operators.FillString.mutatorName)) || + (!stringIsEmpty && operations.includes(operators.EmptyString.mutatorName)) + ) { + yield stringIsEmpty ? operators.FillString.replacementOperator : operators.EmptyString.replacementOperator; + } } }, }; 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 fd12ec05e3..af720fc834 100644 --- a/packages/instrumenter/test/unit/mutators/string-literal-mutator.spec.ts +++ b/packages/instrumenter/test/unit/mutators/string-literal-mutator.spec.ts @@ -1,6 +1,6 @@ import { expect } from 'chai'; -import { expectJSMutation } from '../../helpers/expect-mutation.js'; +import { expectJSMutation, expectJSMutationWithLevel } from '../../helpers/expect-mutation.js'; import { stringLiteralMutator as sut } from '../../../src/mutators/string-literal-mutator.js'; describe(sut.name, () => { @@ -112,4 +112,30 @@ describe(sut.name, () => { expectJSMutation(sut, ''); }); }); + + describe('mutation level', () => { + it('should only mutate EmptyString and EmptyInterpolation from all possible mutations', () => { + expectJSMutationWithLevel( + sut, + ['EmptyString', 'EmptyInterpolation'], + '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 + ); + }); + it('should block the mutators', () => { + expectJSMutationWithLevel(sut, [], 'const bar = "bar"; const foo = `name: ${level_name}`; const emptyString=""; const emptyInterp=``'); + }); + it('should mutate everything', () => { + expectJSMutationWithLevel( + sut, + undefined, + '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 literal + 'const bar = "bar"; const foo = ``; const emptyString=""; const emptyInterp=``', // empties interpolation + 'const bar = "bar"; const foo = `name: ${level_name}`; const emptyString="Stryker was here!"; const emptyInterp=``', // fills string literal + 'const bar = "bar"; const foo = `name: ${level_name}`; const emptyString=""; const emptyInterp=`Stryker was here!`', // fills interpolation + ); + }); + }); }); diff --git a/testing-project/stryker.conf.json b/testing-project/stryker.conf.json index 7ed58556ee..bb7537398e 100644 --- a/testing-project/stryker.conf.json +++ b/testing-project/stryker.conf.json @@ -20,6 +20,7 @@ "name": "default", "ArithmeticOperator": ["+To-", "-To+", "*To/"], "ArrayDeclaration": ["EmptyArray", "FilledArray", "FilledArrayConstructor"], + "StringLiteral": ["EmptyString", "FillString", "EmptyInterpolation", "FillInterpolation"] "AssignmentOperator": ["-=To+=", "<<=To>>=", "&&=To||="], "BooleanLiteral": ["TrueToFalse", "RemoveNegation"] },