From 71f8f9a6f4f663942c83d64667058d1de3d958a6 Mon Sep 17 00:00:00 2001 From: Gareth Parker Date: Mon, 7 Dec 2020 17:13:32 +0000 Subject: [PATCH] fix(arithmatic-mutator): Don't mutate obvious string concat (#2648) Don't mutate `"a" + "b"` into `"a" - "b"`. This only works for 'obvious' string concats. --- e2e/test/angular-project/verify/verify.ts | 8 ++++---- e2e/test/jest-react/verify/verify.ts | 16 ++++++++-------- .../src/mutators/arithmetic-operator-mutator.ts | 17 ++++++++++++++--- .../arithmatic-operator-mutator.spec.ts | 12 ++++++++++++ 4 files changed, 38 insertions(+), 15 deletions(-) diff --git a/e2e/test/angular-project/verify/verify.ts b/e2e/test/angular-project/verify/verify.ts index 84da9d17f5..9f9abfe3ab 100644 --- a/e2e/test/angular-project/verify/verify.ts +++ b/e2e/test/angular-project/verify/verify.ts @@ -3,15 +3,15 @@ import { expectMetrics } from '../../../helpers'; describe('After running stryker on angular project', () => { it('should report mutation score', async () => { await expectMetrics({ - mutationScore: 42.86, - killed: 3, + mutationScore: 33.33, + killed: 2, survived: 0, noCoverage: 4, compileErrors: 0, - }) + }); // -------------------|---------|----------|-----------|------------|----------|---------| // File | % score | # killed | # timeout | # survived | # no cov | # error | // -------------------|---------|----------|-----------|------------|----------|---------| - // All files | 42.86 | 3 | 0 | 0 | 4 | 0 | + // All files | 33.33 | 2 | 0 | 0 | 4 | 0 | }); }); diff --git a/e2e/test/jest-react/verify/verify.ts b/e2e/test/jest-react/verify/verify.ts index e74281d443..9be7c8fc8b 100644 --- a/e2e/test/jest-react/verify/verify.ts +++ b/e2e/test/jest-react/verify/verify.ts @@ -4,22 +4,22 @@ describe('After running stryker on jest-react project', () => { it('should report expected scores', async () => { await expectMetricsResult({ metrics: produceMetrics({ - killed: 33, + killed: 32, timeout: 0, - mutationScore: 67.35, - mutationScoreBasedOnCoveredCode: 67.35, + mutationScore: 66.67, + mutationScoreBasedOnCoveredCode: 66.67, survived: 16, - totalCovered: 49, - totalDetected: 33, - totalMutants: 49, + totalCovered: 48, + totalDetected: 32, + totalMutants: 48, totalUndetected: 16, - totalValid: 49 + totalValid: 48 }), }); /* ---------------|---------|----------|-----------|------------|----------|---------| File | % score | # killed | # timeout | # survived | # no cov | # error | ---------------|---------|----------|-----------|------------|----------|---------| - All files | 67.35 | 33 | 0 | 16 | 0 | 0 |*/ + All files | 66.67 | 32 | 0 | 16 | 0 | 0 |*/ }); }); diff --git a/packages/instrumenter/src/mutators/arithmetic-operator-mutator.ts b/packages/instrumenter/src/mutators/arithmetic-operator-mutator.ts index da2f4de222..d752fcda8c 100644 --- a/packages/instrumenter/src/mutators/arithmetic-operator-mutator.ts +++ b/packages/instrumenter/src/mutators/arithmetic-operator-mutator.ts @@ -18,7 +18,7 @@ export class ArithmeticOperatorMutator implements NodeMutator { public name = 'ArithmeticOperator'; public mutate(path: NodePath): NodeMutation[] { - if (path.isBinaryExpression() && this.isSupported(path.node.operator)) { + if (path.isBinaryExpression() && this.isSupported(path.node.operator, path.node)) { const mutatedOperator = this.operators[path.node.operator]; const replacement = types.cloneNode(path.node, false); replacement.operator = mutatedOperator; @@ -28,7 +28,18 @@ export class ArithmeticOperatorMutator implements NodeMutator { return []; } - private isSupported(operator: string): operator is keyof typeof ArithmeticOperators { - return Object.keys(this.operators).includes(operator); + private isSupported(operator: string, node: types.BinaryExpression): operator is keyof typeof ArithmeticOperators { + if (!Object.keys(this.operators).includes(operator)) { + return false; + } + + const stringTypes = ['StringLiteral', 'TemplateLiteral']; + const leftType = node.left.type === 'BinaryExpression' ? node.left.right.type : node.left.type; + + if (stringTypes.includes(node.right.type) || stringTypes.includes(leftType)) { + return false; + } + + return true; } } diff --git a/packages/instrumenter/test/unit/mutators/arithmatic-operator-mutator.spec.ts b/packages/instrumenter/test/unit/mutators/arithmatic-operator-mutator.spec.ts index da30176a11..a747e76cc8 100644 --- a/packages/instrumenter/test/unit/mutators/arithmatic-operator-mutator.spec.ts +++ b/packages/instrumenter/test/unit/mutators/arithmatic-operator-mutator.spec.ts @@ -23,4 +23,16 @@ describe(ArithmeticOperatorMutator.name, () => { expectJSMutation(sut, 'a / b', 'a * b'); expectJSMutation(sut, 'a % b', 'a * b'); }); + + it('should not mutate string literal concatenation', () => { + expectJSMutation(sut, '"a" + "b"'); + expectJSMutation(sut, 'const a = 1; "a" + a'); + expectJSMutation(sut, '3 + "a"'); + + expectJSMutation(sut, '`a` + `b`'); + expectJSMutation(sut, 'const a = 1; `a` + a'); + expectJSMutation(sut, '3 + `a`'); + + expectJSMutation(sut, '"a" + b + "c" + d + "e"'); + }); });