-
Notifications
You must be signed in to change notification settings - Fork 240
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(mutators): Implement missing AssignmentOperatorMutator (#3203)
Add the [AssignmentOperatorMutator](https://stryker-mutator.io/docs/mutation-testing-elements/supported-mutators/#assignment-expression) | Original | Mutated | | -------- | ------- | | `+=` | `-=` | | `-=` | `+=` | | `*=` | `/=` | | `/=` | `*=` | | `%=` | `*=` | | `<<=` | `>>=` | | `>>=` | `<<=` | | `&=` | `\|=` | | `|=` | `&=` | | `&&=` | `||=` | | `||=` | `&&=` | | `??=` | `&&=` |
- Loading branch information
Showing
6 changed files
with
134 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,24 +1,15 @@ | ||
import { expectMetricsResult, produceMetrics } from '../../../helpers'; | ||
import { expectMetricsJson } from '../../../helpers'; | ||
|
||
describe('Verify stryker has ran correctly', () => { | ||
|
||
it('should report expected score', async () => { | ||
// File | % score | # killed | # timeout | # survived | # no cov | # error | | ||
// All files | 58.54 | 24 | 0 | 17 | 0 | 1 | | ||
await expectMetricsResult({ | ||
metrics: produceMetrics({ | ||
killed: 24, | ||
mutationScore: 55.81, | ||
mutationScoreBasedOnCoveredCode: 55.81, | ||
runtimeErrors: 1, | ||
survived: 19, | ||
totalCovered: 43, | ||
totalDetected: 24, | ||
totalInvalid: 1, | ||
totalMutants: 44, | ||
totalUndetected: 19, | ||
totalValid: 43 | ||
}) | ||
// All files | 57.45 | 27 | 0 | 20 | 0 | 1 | | ||
await expectMetricsJson({ | ||
killed: 27, | ||
mutationScore: 57.45, | ||
runtimeErrors: 1, | ||
survived: 20, | ||
noCoverage: 0, | ||
}); | ||
}); | ||
}); |
47 changes: 47 additions & 0 deletions
47
packages/instrumenter/src/mutators/assignment-operator-mutator.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import * as types from '@babel/types'; | ||
|
||
import { NodeMutator } from '.'; | ||
|
||
enum AssignmentOperators { | ||
'+=' = '-=', | ||
'-=' = '+=', | ||
'*=' = '/=', | ||
'/=' = '*=', | ||
'%=' = '*=', | ||
'<<=' = '>>=', | ||
'>>=' = '<<=', | ||
'&=' = '|=', | ||
'|=' = '&=', | ||
'&&=' = '||=', | ||
'||=' = '&&=', | ||
'??=' = '&&=', | ||
} | ||
|
||
const stringTypes = Object.freeze(['StringLiteral', 'TemplateLiteral']); | ||
const stringAssignmentTypes = Object.freeze(['&&=', '||=', '??=']); | ||
|
||
export const assignmentOperatorMutator: NodeMutator = { | ||
name: 'AssignmentOperator', | ||
|
||
*mutate(path) { | ||
if (path.isAssignmentExpression() && isSupportedAssignmentOperator(path.node.operator) && isSupported(path.node)) { | ||
const mutatedOperator = AssignmentOperators[path.node.operator]; | ||
const replacement = types.cloneNode(path.node, false); | ||
replacement.operator = mutatedOperator; | ||
yield replacement; | ||
} | ||
}, | ||
}; | ||
|
||
function isSupportedAssignmentOperator(operator: string): operator is keyof typeof AssignmentOperators { | ||
return Object.keys(AssignmentOperators).includes(operator); | ||
} | ||
|
||
function isSupported(node: types.AssignmentExpression): boolean { | ||
// Excludes assignment operators that apply to strings. | ||
if (stringTypes.includes(node.right.type) && !stringAssignmentTypes.includes(node.operator)) { | ||
return false; | ||
} | ||
|
||
return true; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
75 changes: 75 additions & 0 deletions
75
packages/instrumenter/test/unit/mutators/assignment-operator-mutator.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import { expect } from 'chai'; | ||
|
||
import { assignmentOperatorMutator as sut } from '../../../src/mutators/assignment-operator-mutator'; | ||
import { expectJSMutation } from '../../helpers/expect-mutation'; | ||
|
||
describe(sut.name, () => { | ||
it('should have name "AssignmentOperator"', () => { | ||
expect(sut.name).eq('AssignmentOperator'); | ||
}); | ||
|
||
it('should mutate += and -=', () => { | ||
expectJSMutation(sut, 'a += b', 'a -= b'); | ||
expectJSMutation(sut, 'a -= b', 'a += b'); | ||
}); | ||
|
||
it('should mutate *=, %= and /=', () => { | ||
expectJSMutation(sut, 'a *= b', 'a /= b'); | ||
expectJSMutation(sut, 'a /= b', 'a *= b'); | ||
expectJSMutation(sut, 'a %= b', 'a *= b'); | ||
}); | ||
|
||
it('should mutate *=, %= and /=', () => { | ||
expectJSMutation(sut, 'a *= b', 'a /= b'); | ||
expectJSMutation(sut, 'a /= b', 'a *= b'); | ||
expectJSMutation(sut, 'a %= b', 'a *= b'); | ||
}); | ||
|
||
it('should mutate <<=, >>=, &= and |=', () => { | ||
expectJSMutation(sut, 'a *= b', 'a /= b'); | ||
expectJSMutation(sut, 'a /= b', 'a *= b'); | ||
expectJSMutation(sut, 'a %= b', 'a *= b'); | ||
}); | ||
|
||
it('should mutate &&=, ||= and ??=', () => { | ||
expectJSMutation(sut, 'a &&= b', 'a ||= b'); | ||
expectJSMutation(sut, 'a ||= b', 'a &&= b'); | ||
expectJSMutation(sut, 'a ??= b', 'a &&= b'); | ||
}); | ||
|
||
it('should not mutate a string literal unless it is &&=, ||=, ??=', () => { | ||
expectJSMutation(sut, 'a += "b"'); | ||
expectJSMutation(sut, 'a -= "b"'); | ||
expectJSMutation(sut, 'a *= "b"'); | ||
expectJSMutation(sut, 'a /= "b"'); | ||
expectJSMutation(sut, 'a %= "b"'); | ||
expectJSMutation(sut, 'a <<= "b"'); | ||
expectJSMutation(sut, 'a >>= "b"'); | ||
expectJSMutation(sut, 'a &= "b"'); | ||
expectJSMutation(sut, 'a |= "b"'); | ||
}); | ||
|
||
it('should mutate a string literal using &&=, ||=, ??=', () => { | ||
expectJSMutation(sut, 'a &&= "b"', 'a ||= "b"'); | ||
expectJSMutation(sut, 'a ||= "b"', 'a &&= "b"'); | ||
expectJSMutation(sut, 'a ??= "b"', 'a &&= "b"'); | ||
}); | ||
|
||
it('should not mutate string template unless it is &&=, ||=, ??=', () => { | ||
expectJSMutation(sut, 'a += `b`'); | ||
expectJSMutation(sut, 'a -= `b`'); | ||
expectJSMutation(sut, 'a *= `b`'); | ||
expectJSMutation(sut, 'a /= `b`'); | ||
expectJSMutation(sut, 'a %= `b`'); | ||
expectJSMutation(sut, 'a <<= `b`'); | ||
expectJSMutation(sut, 'a >>= `b`'); | ||
expectJSMutation(sut, 'a &= `b`'); | ||
expectJSMutation(sut, 'a |= `b`'); | ||
}); | ||
|
||
it('should mutate string template using &&=, ||=, ??=', () => { | ||
expectJSMutation(sut, 'a &&= `b`', 'a ||= `b`'); | ||
expectJSMutation(sut, 'a ||= `b`', 'a &&= `b`'); | ||
expectJSMutation(sut, 'a ??= `b`', 'a &&= `b`'); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters