Skip to content

Commit

Permalink
#22 restrict conditionalexpression mutator (#55)
Browse files Browse the repository at this point in the history
* Added an extra suboperator

* changed implementation of suboperator

* tests for conditional operator for mutationLevel

* combined config file

* moved order of yielding

* modified tests properly

---------

Co-authored-by: Danut Copae <[email protected]>
  • Loading branch information
Ja4pp and dvcopae authored Dec 5, 2023
1 parent 6b7d9a2 commit b91605f
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 15 deletions.
5 changes: 5 additions & 0 deletions packages/api/schema/stryker-core.json
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,11 @@
"const" : "BooleanExpressionToFalse",
"title": "BooleanExpressionToFalseMutator",
"description": "Replace ```var x = a > b ? 1 : 2;``` with ```var x = false ? 1 : 2;```."
},
{
"const" : "SwitchToEmpty",
"title": "SwitchToEmptyMutator",
"description": "Replace ```switch(x) with switch()```."
}
]
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,43 +8,85 @@ const booleanOperators = Object.freeze(['!=', '!==', '&&', '<', '<=', '==', '===

const { types } = babel;

const conditionalReplacements = Object.assign({
BooleanExpressionToFalse: { replacementOperator: types.booleanLiteral(false), mutatorName: 'BooleanExpressionToFalse' },
BooleanExpressionToTrue: { replacementOperator: types.booleanLiteral(true), mutatorName: 'BooleanExpressionToTrue' },
DoWhileLoopToFalse: { replacementOperator: types.booleanLiteral(false), mutatorName: 'DoWhileLoopToFalse' },
ForLoopToFalse: { replacementOperator: types.booleanLiteral(false), mutatorName: 'ForLoopToFalse' },
IfToFalse: { replacementOperator: types.booleanLiteral(false), mutatorName: 'IfToFalse' },
IfToTrue: { replacementOperator: types.booleanLiteral(true), mutatorName: 'IfToTrue' },
WhileLoopToFalse: { replacementOperator: types.booleanLiteral(false), mutatorName: 'WhileLoopToFalse' },
SwitchToEmpty: { replacementOperator: [], mutatorName: 'SwitchToEmpty' },
} as const);

export const conditionalExpressionMutator: NodeMutator = {
name: 'ConditionalExpression',

*mutate(path) {
*mutate(path, operations) {
if (isTestOfLoop(path)) {
yield types.booleanLiteral(false);
if (
isTestOfWhileLoop(path) &&
(operations === undefined || operations.includes(conditionalReplacements.WhileLoopToFalse.mutatorName as string))
) {
yield conditionalReplacements.WhileLoopToFalse.replacementOperator;
}

if (
isTestOfDoWhileLoop(path) &&
(operations === undefined || operations.includes(conditionalReplacements.DoWhileLoopToFalse.mutatorName as string))
) {
yield conditionalReplacements.DoWhileLoopToFalse.replacementOperator;
}
if (isTestOfForLoop(path) && (operations === undefined || operations.includes(conditionalReplacements.ForLoopToFalse.mutatorName as string))) {
yield conditionalReplacements.ForLoopToFalse.replacementOperator;
}
} else if (isTestOfCondition(path)) {
yield types.booleanLiteral(true);
yield types.booleanLiteral(false);
if (operations === undefined || operations.includes(conditionalReplacements.IfToTrue.mutatorName as string)) {
yield conditionalReplacements.IfToTrue.replacementOperator;
}
if (operations === undefined || operations.includes(conditionalReplacements.IfToFalse.mutatorName as string)) {
yield conditionalReplacements.IfToFalse.replacementOperator;
}
} else if (isBooleanExpression(path)) {
if (path.parent?.type === 'LogicalExpression') {
// For (x || y), do not generate the (true || y) mutation as it
// has the same behavior as the (true) mutator, handled in the
// isTestOfCondition branch above
if (path.parent.operator === '||') {
yield types.booleanLiteral(false);
if (operations === undefined || operations.includes(conditionalReplacements.BooleanExpressionToFalse.mutatorName as string)) {
yield conditionalReplacements.BooleanExpressionToFalse.replacementOperator;
}
return;
}
// For (x && y), do not generate the (false && y) mutation as it
// has the same behavior as the (false) mutator, handled in the
// isTestOfCondition branch above
if (path.parent.operator === '&&') {
yield types.booleanLiteral(true);
if (operations === undefined || operations.includes(conditionalReplacements.BooleanExpressionToTrue.mutatorName as string)) {
yield conditionalReplacements.BooleanExpressionToTrue.replacementOperator;
}
return;
}
}
yield types.booleanLiteral(true);
yield types.booleanLiteral(false);
if (operations === undefined || operations.includes(conditionalReplacements.BooleanExpressionToTrue.mutatorName as string)) {
yield conditionalReplacements.BooleanExpressionToTrue.replacementOperator;
}
if (operations === undefined || operations.includes(conditionalReplacements.BooleanExpressionToFalse.mutatorName as string)) {
yield conditionalReplacements.BooleanExpressionToFalse.replacementOperator;
}
} else if (path.isForStatement() && !path.node.test) {
const replacement = deepCloneNode(path.node);
replacement.test = types.booleanLiteral(false);
yield replacement;
if (operations === undefined || operations.includes(conditionalReplacements.ForLoopToFalse.mutatorName as string)) {
const replacement = deepCloneNode(path.node);
replacement.test = conditionalReplacements.ForLoopToFalse.replacementOperator;
yield replacement;
}
} else if (path.isSwitchCase() && path.node.consequent.length > 0) {
// if not a fallthrough case
const replacement = deepCloneNode(path.node);
replacement.consequent = [];
yield replacement;
if (operations === undefined || operations.includes(conditionalReplacements.SwitchToEmpty.mutatorName as string)) {
const replacement = deepCloneNode(path.node);
replacement.consequent = conditionalReplacements.SwitchToEmpty.replacementOperator;
yield replacement;
}
}
},
};
Expand All @@ -57,6 +99,30 @@ function isTestOfLoop(path: NodePath): boolean {
return (parentPath.isForStatement() || parentPath.isWhileStatement() || parentPath.isDoWhileStatement()) && parentPath.node.test === path.node;
}

function isTestOfWhileLoop(path: NodePath): boolean {
const { parentPath } = path;
if (!parentPath) {
return false;
}
return parentPath.isWhileStatement() && parentPath.node.test === path.node;
}

function isTestOfForLoop(path: NodePath): boolean {
const { parentPath } = path;
if (!parentPath) {
return false;
}
return parentPath.isForStatement() && parentPath.node.test === path.node;
}

function isTestOfDoWhileLoop(path: NodePath): boolean {
const { parentPath } = path;
if (!parentPath) {
return false;
}
return parentPath.isDoWhileStatement() && parentPath.node.test === path.node;
}

function isTestOfCondition(path: NodePath): boolean {
const { parentPath } = path;
if (!parentPath) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { expect } from 'chai';

import { expectJSMutation } from '../../helpers/expect-mutation.js';
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;

describe(sut.name, () => {
it('should have name "ConditionalExpression"', () => {
expect(sut.name).eq('ConditionalExpression');
Expand Down Expand Up @@ -140,4 +144,44 @@ describe(sut.name, () => {
it('should mutate the expression of a while statement', () => {
expectJSMutation(sut, 'while(a < b) { console.log(); }', 'while(false) { console.log(); }');
});

it('should only mutate for, if and switch statement', () => {
expectJSMutationWithLevel(
sut,
conditionLevel,
'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
'for (var i = 0; i < 10; i++) { };if(true); switch (x) {case 0: 2}', // mutates if statement to true
'for (var i = 0; i < 10; i++) { };if(x > 2); switch (x) {case 0:}', // mutates switch statement
);
});

it('should only mutate while, while do and boolean expression', () => {
expectJSMutationWithLevel(
sut,
conditionLevel2,
'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
'while (a > b) { }; do { } while (false); var x = a > b ? 1 : 2', // mutates while do loop
'while (a > b) { }; do { } while (a > b); var x = true ? 1 : 2', // mutates boolean to false
);
});

it('should only mutate all', () => {
expectJSMutationWithLevel(
sut,
conditionLevel3,
'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
'for (var i = 0; i < 10; i++) { };if(true); switch (x) {case 0: 2}; while (a > b); { } do { } while (a > b); var x = a > b ? 1 : 2', // mutates if statement to true
'for (var i = 0; i < 10; i++) { };if(x > 2); switch (x) {case 0:}; while (a > b); { } do { } while (a > b); var x = a > b ? 1 : 2', // mutates switch statement
'for (var i = 0; i < 10; i++) { };if(x > 2); switch (x) {case 0: 2}; while (false); { } do { } while (a > b); var x = a > b ? 1 : 2', // mutates while loop
'for (var i = 0; i < 10; i++) { };if(x > 2); switch (x) {case 0: 2}; while (a > b); { } do { } while (a > b); var x = false ? 1 : 2', // mutates boolean to false
'for (var i = 0; i < 10; i++) { };if(x > 2); switch (x) {case 0: 2}; while (a > b); { } do { } while (false); var x = a > b ? 1 : 2', // mutates while do loop
'for (var i = 0; i < 10; i++) { };if(x > 2); switch (x) {case 0: 2}; while (a > b); { } do { } while (a > b); var x = true ? 1 : 2', // mutates boolean to false
);
});
});

0 comments on commit b91605f

Please sign in to comment.