From 980591775b19aea6661ed6283b38aa687df65b58 Mon Sep 17 00:00:00 2001 From: Jelle Peters Date: Wed, 1 Feb 2017 17:42:27 +0100 Subject: [PATCH] feat(ArrayDeclarationMutator): Add new mutator. (#229) Add a new mutator which mutates non-empty arrays to be empty --- src/MutatorOrchestrator.ts | 2 + src/mutators/ArrayDeclaratorMutator.ts | 27 +++++ test/integration/utils/fileUtilsSpec.ts | 2 +- .../mutators/ArrayDeclaratorMutatorSpec.ts | 101 ++++++++++++++++++ testResources/sampleProject/src/Array.js | 5 + 5 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 src/mutators/ArrayDeclaratorMutator.ts create mode 100644 test/unit/mutators/ArrayDeclaratorMutatorSpec.ts create mode 100644 testResources/sampleProject/src/Array.js diff --git a/src/MutatorOrchestrator.ts b/src/MutatorOrchestrator.ts index acbbd214d4..6e342d96c8 100644 --- a/src/MutatorOrchestrator.ts +++ b/src/MutatorOrchestrator.ts @@ -4,6 +4,7 @@ import LogicalOperatorMutator from './mutators/LogicalOperatorMutator'; import RemoveConditionalsMutator from './mutators/RemoveConditionalsMutator'; import UnaryOperatorMutator from './mutators/UnaryOperatorMutator'; import UpdateOperatorMutator from './mutators/UpdateOperatorMutator'; +import ArrayDeclaratorMutator from './mutators/ArrayDeclaratorMutator'; import { Mutator, MutatorFactory } from 'stryker-api/mutant'; import { Reporter, SourceFile } from 'stryker-api/report'; import * as fileUtils from './utils/fileUtils'; @@ -84,6 +85,7 @@ export default class MutatorOrchestrator { mutatorFactory.register('RemoveConditionals', RemoveConditionalsMutator); mutatorFactory.register('UnaryOperator', UnaryOperatorMutator); mutatorFactory.register('UpdateOperator', UpdateOperatorMutator); + mutatorFactory.register('ArrayDeclarator', ArrayDeclaratorMutator); } /** diff --git a/src/mutators/ArrayDeclaratorMutator.ts b/src/mutators/ArrayDeclaratorMutator.ts new file mode 100644 index 0000000000..5d0422613a --- /dev/null +++ b/src/mutators/ArrayDeclaratorMutator.ts @@ -0,0 +1,27 @@ +import {Syntax} from 'esprima'; +import {Mutator} from 'stryker-api/mutant'; +import * as estree from 'estree'; + +/** + * Represents a mutator which can remove the content of an array's elements. + */ +export default class ArrayDeclaratorMutator implements Mutator { + name = 'ArrayDeclarator'; + + constructor() { } + + applyMutations(node: estree.Node, copy: (obj: T, deep?: boolean) => T): void | estree.Node | estree.Node[] { + if ((node.type === Syntax.CallExpression || node.type === Syntax.NewExpression) && node.callee.type === Syntax.Identifier && node.callee.name === 'Array' && node.arguments.length > 0) { + let mutatedNode = copy(node); + mutatedNode.arguments = []; + return mutatedNode; + } + + if (node.type === Syntax.ArrayExpression && node.elements.length > 0) { + let mutatedNode = copy(node); + mutatedNode.elements = []; + return mutatedNode; + } + } +} + diff --git a/test/integration/utils/fileUtilsSpec.ts b/test/integration/utils/fileUtilsSpec.ts index 1210f41037..f7f654737b 100644 --- a/test/integration/utils/fileUtilsSpec.ts +++ b/test/integration/utils/fileUtilsSpec.ts @@ -37,7 +37,7 @@ describe('fileUtils', () => { describe('glob', () => { it('should resolve files', () => - expect(fileUtils.glob('testResources/sampleProject/**/*.js')).to.eventually.have.length(9)); + expect(fileUtils.glob('testResources/sampleProject/**/*.js')).to.eventually.have.length(10)); it('should not resolve to directories', () => expect(fileUtils.glob('testResources/vendor/**/*.js')).to.eventually.have.length(1)); diff --git a/test/unit/mutators/ArrayDeclaratorMutatorSpec.ts b/test/unit/mutators/ArrayDeclaratorMutatorSpec.ts new file mode 100644 index 0000000000..942a7ddc76 --- /dev/null +++ b/test/unit/mutators/ArrayDeclaratorMutatorSpec.ts @@ -0,0 +1,101 @@ +import ArrayDeclaratorMutator from '../../../src/mutators/ArrayDeclaratorMutator'; +import { expect } from 'chai'; +import * as parser from '../../../src/utils/parserUtils'; +import { copy } from '../../../src/utils/objectUtils'; +import { Syntax } from 'esprima'; +import * as estree from 'estree'; + +describe('BlockStatementMutator', () => { + let sut: ArrayDeclaratorMutator; + + beforeEach(() => sut = new ArrayDeclaratorMutator()); + + const getVariableDeclaration = (program: estree.Program) => (program.body[0] as estree.VariableDeclaration); + + const getArrayExpression = (program: estree.Program) => { + const variableDeclaration = getVariableDeclaration(program); + return (variableDeclaration.declarations[0].init as estree.ArrayExpression); + }; + + const getArrayCallExpression = (program: estree.Program) => { + const variableDeclaration = getVariableDeclaration(program); + return (variableDeclaration.declarations[0].init as estree.SimpleCallExpression); + }; + + const getArrayNewExpression = (program: estree.Program) => { + const variableDeclaration = getVariableDeclaration(program); + return (variableDeclaration.declarations[0].init as estree.NewExpression); + }; + + it('should mutate when supplied with an array expression', () => { + // Arrange + const program = parser.parse(`var array = [1,2,3];`); + const arrayExpression = getArrayExpression(program); + + // Act + const actual = sut.applyMutations(arrayExpression, copy); + + // Assert + expect(actual).to.be.ok; + expect(actual.nodeID).to.eq(arrayExpression.nodeID); + expect(actual.elements).to.have.length(0); + }); + + it('should mutate when supplied with an array `call` expression', () => { + // Arrange + const program = parser.parse(`var array = Array(1,2,3);`); + const arrayExpression = getArrayCallExpression(program); + + // Act + const actual = sut.applyMutations(arrayExpression, copy); + + // Assert + expect(actual).to.be.ok; + expect(actual.nodeID).to.eq(arrayExpression.nodeID); + expect(actual.arguments).to.have.length(0); + }); + + it('should mutate when supplied with an array `new` expression', () => { + // Arrange + const program = parser.parse(`var array = new Array(1,2,3);`); + const arrayExpression = getArrayNewExpression(program); + + // Act + const actual = sut.applyMutations(arrayExpression, copy); + + // Assert + expect(actual).to.be.ok; + expect(actual.nodeID).to.eq(arrayExpression.nodeID); + expect(actual.arguments).to.have.length(0); + }); + + it('should not mutate an empty expression', () => { + // Arrange + const program = parser.parse(`var array = []`); + const emptyArrayExpression = getArrayExpression(program); + + // Act + const actual = sut.applyMutations(emptyArrayExpression, copy); + expect(actual).to.be.undefined; + }); + + it('should not mutate an empty `call` expression', () => { + // Arrange + const program = parser.parse(`var array = Array()`); + const emptyCallExpression = getArrayExpression(program); + + // Act + const actual = sut.applyMutations(emptyCallExpression, copy); + expect(actual).to.be.undefined; + }); + + it('should not mutate an empty `new` expression', () => { + // Arrange + const program = parser.parse(`var array = new Array()`); + const emptyNewExpression = getArrayExpression(program); + + // Act + const actual = sut.applyMutations(emptyNewExpression, copy); + expect(actual).to.be.undefined; + }); +}); \ No newline at end of file diff --git a/testResources/sampleProject/src/Array.js b/testResources/sampleProject/src/Array.js new file mode 100644 index 0000000000..0069225dd8 --- /dev/null +++ b/testResources/sampleProject/src/Array.js @@ -0,0 +1,5 @@ +var array = [1,2,3]; + +var array2 = Array(1,2,3); + +var array3 = new Array(1,2,3);