Skip to content

Commit

Permalink
initial implementation of condition
Browse files Browse the repository at this point in the history
  • Loading branch information
Ja4pp committed Nov 26, 2023
1 parent 65dcb0b commit bcb6cc0
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 16 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 @@ -448,6 +448,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.BooleanExpressionToFalse.mutatorName as string)) {
yield conditionalReplacements.BooleanExpressionToFalse.replacementOperator;
}
if (operations === undefined || operations.includes(conditionalReplacements.BooleanExpressionToTrue.mutatorName as string)) {
yield conditionalReplacements.BooleanExpressionToTrue.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,19 @@
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[] = [
'WhileLoopToFalse',
'BooleanExpressionToFalse',
'DoWhileLoopToFalse',
'BooleanExpressionToTrue',
'ForLoopToFalse',
'IfToFalse',
'IfToTrue',
'SwitchToEmpty',
];

describe(sut.name, () => {
it('should have name "ConditionalExpression"', () => {
expect(sut.name).eq('ConditionalExpression');
Expand Down Expand Up @@ -140,4 +151,36 @@ 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 --a and ++a', () => {
expectJSMutationWithLevel(
sut,
conditionLevel,
'--a; ++a; a--; a++',
'++a; ++a; a--; a++', //mutates --a
'--a; --a; a--; a++', //mutates ++a
);
});

it('should only mutate a-- and a++', () => {
expectJSMutationWithLevel(
sut,
updateLevel,
'--a; ++a; a--; a++',
'--a; ++a; a--; a--', //mutates a++
'--a; ++a; a++; a++', //mutates a--
);
});

it('should mutate all', () => {
expectJSMutationWithLevel(
sut,
updateLevel,
'--a; ++a; a--; a++',
'++a; ++a; a--; a++', //mutates --a
'--a; --a; a--; a++', //mutates ++a
'--a; ++a; a--; a--', //mutates a++
'--a; ++a; a++; a++', //mutates a--
);
});
});
2 changes: 1 addition & 1 deletion testing-project/stryker.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"mutationLevels": [
{
"name": "default",
"UpdateOperator": ["Pre--To++", "Pre++To--", "Post++To--", "Post--To++"]
"ConditionalExpression": ["WhileLoopToFalse", "BooleanExpressionToFalse", "DoWhileLoopToFalse", "BooleanExpressionToTrue", "ForLoopToFalse","IfToFalse","IfToTrue", "SwitchToEmpty"]
},
{
"name": "fancy",
Expand Down

0 comments on commit bcb6cc0

Please sign in to comment.