diff --git a/integrationTest/test/command/verify/verify.ts b/integrationTest/test/command/verify/verify.ts index 52198a635f..cc669c0db5 100644 --- a/integrationTest/test/command/verify/verify.ts +++ b/integrationTest/test/command/verify/verify.ts @@ -4,11 +4,11 @@ import * as path from 'path'; import { readScoreResult } from '../../../helpers'; describe('After running stryker with the command test runner', () => { - it('should report 69% mutation score', async () => { + it('should report 64% mutation score', async () => { const scoreResult = await readScoreResult(path.resolve('reports', 'mutation', 'events')); expect(scoreResult.killed).eq(16); expect(scoreResult.noCoverage).eq(0); - expect(scoreResult.mutationScore).greaterThan(69).and.lessThan(70); + expect(scoreResult.mutationScore).eq(64); }); it('should write to a log file', async () => { diff --git a/packages/stryker-javascript-mutator/src/mutators/ConditionalExpressionMutator.ts b/packages/stryker-javascript-mutator/src/mutators/ConditionalExpressionMutator.ts index 50cfcd2fa5..03c84efecf 100644 --- a/packages/stryker-javascript-mutator/src/mutators/ConditionalExpressionMutator.ts +++ b/packages/stryker-javascript-mutator/src/mutators/ConditionalExpressionMutator.ts @@ -1,21 +1,55 @@ import * as types from '@babel/types'; import NodeMutator from './NodeMutator'; import NodeGenerator from '../helpers/NodeGenerator'; +import { NodeWithParent } from '../helpers/ParentNode'; /** * Represents a mutator which can remove the conditional clause from statements. */ export default class ConditionalExpressionMutator implements NodeMutator { + private readonly validOperators: string[] = [ + '!=', + '!==', + '&&', + '<', + '<=', + '==', + '===', + '>', + '>=', + '||', + ]; + public name = 'ConditionalExpression'; - constructor() { } + constructor() {} + + private hasValidParent(node: NodeWithParent): boolean { + return ( + !node.parent || + !( + types.isForStatement(node.parent) || + types.isWhileStatement(node.parent) || + types.isIfStatement(node.parent) || + types.isDoWhileStatement(node.parent) + ) + ); + } + + private isValidOperator(operator: string): boolean { + return this.validOperators.indexOf(operator) !== -1; + } public mutate(node: types.Node): types.Node[] | void { - if (types.isConditionalExpression(node)) { - return [ - NodeGenerator.createBooleanLiteralNode(node.test, false), - NodeGenerator.createBooleanLiteralNode(node.test, true) - ]; + if ( + (types.isBinaryExpression(node) || types.isLogicalExpression(node)) && + this.hasValidParent(node) && + this.isValidOperator(node.operator) + ) { + return [ + NodeGenerator.createBooleanLiteralNode(node, false), + NodeGenerator.createBooleanLiteralNode(node, true), + ]; } } } diff --git a/packages/stryker-mutator-specification/src/ConditionalExpressionMutatorSpec.ts b/packages/stryker-mutator-specification/src/ConditionalExpressionMutatorSpec.ts index 65661aed34..be6b6e9388 100644 --- a/packages/stryker-mutator-specification/src/ConditionalExpressionMutatorSpec.ts +++ b/packages/stryker-mutator-specification/src/ConditionalExpressionMutatorSpec.ts @@ -9,8 +9,60 @@ export default function ConditionalExpressionMutatorSpec(name: string, expectMut expect(name).eq('ConditionalExpression'); }); - it('should replace conditional expressions', () => { + it('should mutate ternary operator', () => { expectMutation('a < 3? b : c', 'false? b : c', 'true? b : c'); }); + + it('should mutate < and >', () => { + expectMutation('a < b', 'true', 'false'); + expectMutation('a > b', 'true', 'false'); + }); + + it('should mutate <= and >=', () => { + expectMutation('a <= b', 'true', 'false'); + expectMutation('a >= b', 'true', 'false'); + }); + + it('should mutate == and ===', () => { + expectMutation('a == b', 'true', 'false'); + expectMutation('a === b', 'true', 'false'); + }); + + it('should mutate != and !==', () => { + expectMutation('a != b', 'true', 'false'); + expectMutation('a !== b', 'true', 'false'); + }); + + it('should mutate && and ||', () => { + expectMutation('a && b', 'true', 'false'); + expectMutation('a || b', 'true', 'false'); + }); + + it('should not mutate + and -', () => { + expectMutation('a + b'); + expectMutation('a - b'); + }); + + it('should not mutate *, % and /', () => { + expectMutation('a * b'); + expectMutation('a / b'); + expectMutation('a % b'); + }); + + it('should not mutate `if statement`', () => { + expectMutation('if (a < 6) { a++ }'); + }); + + it('should not mutate `for statement`', () => { + expectMutation('for(let i=0;i<10; i++) { console.log(); }'); + }); + + it('should not mutate `while statement`', () => { + expectMutation('while(a < b) { console.log(); }'); + }); + + it('should not mutate `do while statement`', () => { + expectMutation('do { console.log(); } while(a < b);'); + }); }); } diff --git a/packages/stryker-typescript/src/mutator/ConditionalExpressionMutator.ts b/packages/stryker-typescript/src/mutator/ConditionalExpressionMutator.ts index 0c1bd2c37d..0dee50a723 100644 --- a/packages/stryker-typescript/src/mutator/ConditionalExpressionMutator.ts +++ b/packages/stryker-typescript/src/mutator/ConditionalExpressionMutator.ts @@ -1,17 +1,47 @@ import * as ts from 'typescript'; import NodeMutator, { NodeReplacement } from './NodeMutator'; -export default class ConditionalExpressionMutator extends NodeMutator { +export default class ConditionalExpressionMutator extends NodeMutator { public name = 'ConditionalExpression'; - public guard(node: ts.Node): node is ts.ConditionalExpression { - return node.kind === ts.SyntaxKind.ConditionalExpression; + public guard(node: ts.Node): node is ts.BinaryExpression { + return node.kind === ts.SyntaxKind.BinaryExpression; } - protected identifyReplacements(node: ts.ConditionalExpression): NodeReplacement[] { + private isInvalidParent(parent: ts.Node): boolean { + switch (parent.kind) { + case ts.SyntaxKind.IfStatement: + case ts.SyntaxKind.ForStatement: + case ts.SyntaxKind.WhileStatement: + case ts.SyntaxKind.DoStatement: + case ts.SyntaxKind.LiteralType: + return true; + default: + return false; + } + } + + private isInvalidOperator(operatorToken: ts.BinaryOperatorToken): boolean { + switch (operatorToken.kind) { + case ts.SyntaxKind.PlusToken: + case ts.SyntaxKind.MinusToken: + case ts.SyntaxKind.SlashToken: + case ts.SyntaxKind.AsteriskToken: + case ts.SyntaxKind.PercentToken: + return true; + default: + return false; + } + } + + protected identifyReplacements(node: ts.BinaryExpression): NodeReplacement[] { + if ((node.parent && this.isInvalidParent(node.parent)) || this.isInvalidOperator(node.operatorToken)) { + return []; + } + return [ - { node: node.condition, replacement: 'false' }, - { node: node.condition, replacement: 'true' } + { node, replacement: 'false' }, + { node, replacement: 'true' } ]; }