Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(#1472): add excluding specific mutations #1660

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 61 additions & 8 deletions packages/javascript-mutator/src/JavaScriptMutator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,59 @@ import BabelHelper from './helpers/BabelHelper';
import { tokens, commonTokens } from '@stryker-mutator/api/plugin';

export class JavaScriptMutator implements Mutator {
private readonly mutators: ReadonlyArray<NodeMutator>;
private readonly mutatorsSwitch: { [key: string]: boolean };
private nextMutation = -1 | 0 | 1;

public static inject = tokens(commonTokens.logger, NODE_MUTATORS_TOKEN) ;
constructor(
private readonly log: Logger,
private readonly mutators: ReadonlyArray<NodeMutator>
) { }
constructor(private readonly log: Logger, mutators: ReadonlyArray<NodeMutator>) {
this.mutators = mutators;
this.nextMutation = 0;
this.mutatorsSwitch = {};
mutators.forEach(mutator => {
this.mutatorsSwitch[mutator.name] = true;
});
}

private parseSwitchers(data: string) {
return data.substr(data.indexOf(' ') + 1).split(',').map(el => el.trim());
}

private updateMutators(data: string[], newValue: boolean) {
if (data[0].startsWith('stryker:')) {
data.shift();
}
if (data.length === 0) {
Object.keys(this.mutatorsSwitch).forEach(mutator => {
this.mutatorsSwitch[mutator] = newValue;
});
} else {
data.forEach(mutator => {
if (this.mutatorsSwitch[mutator] === !newValue) {
this.mutatorsSwitch[mutator] = newValue;
}
});
}
}

private mutatorsSwitcher(comments: ReadonlyArray<types.Comment>) {
comments.map(comment => {
const trim = comment.value.trim();
switch (trim.split(' ', 1)[0]) {
case 'stryker:on':
this.updateMutators(this.parseSwitchers(trim), true);
break;
case 'stryker:off':
this.updateMutators(this.parseSwitchers(trim), false);
break;
case 'stryker:next-on':
this.nextMutation = 1;
break;
case 'stryker:next-off':
this.nextMutation = -1;
}
});
}

public mutate(inputFiles: File[]): Mutant[] {
const mutants: Mutant[] = [];
Expand All @@ -22,12 +69,18 @@ export class JavaScriptMutator implements Mutator {
const ast = BabelHelper.parse(file.textContent);

BabelHelper.getNodes(ast).forEach(node => {
this.mutatorsSwitcher(node.leadingComments || []);
this.mutators.forEach(mutator => {
const mutatedNodes = mutator.mutate(node, copy);
if (this.mutatorsSwitch[mutator.name] === true || this.nextMutation === 1) {
const mutatedNodes = mutator.mutate(node, copy);

if (mutatedNodes) {
const newMutants = this.generateMutants(mutatedNodes, mutator.name, file.name);
mutants.push(...newMutants);
if (mutatedNodes.length > 0) {
if (this.nextMutation !== -1) {
const newMutants = this.generateMutants(mutatedNodes, mutator.name, file.name);
mutants.push(...newMutants);
}
this.nextMutation = 0;
}
}
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { NodeMutator } from './NodeMutator';
export default class ArrayLiteralMutator implements NodeMutator {
public name = 'ArrayLiteral';

public mutate(node: types.Node, copy: <T extends types.Node>(obj: T, deep?: boolean) => T): void | types.Node[] {
public mutate(node: types.Node, copy: <T extends types.Node>(obj: T, deep?: boolean) => T): types.Node[] {
const nodes: types.Node[] = [];

if (types.isArrayExpression(node) && node.elements.length > 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { NodeMutator } from './NodeMutator';
export default class ArrayNewExpressionMutator implements NodeMutator {
public name = 'ArrayNewExpression';

public mutate(node: types.Node, copy: <T extends types.Node>(obj: T, deep?: boolean) => T): void | types.Node[] {
public mutate(node: types.Node, copy: <T extends types.Node>(obj: T, deep?: boolean) => T): types.Node[] {
const nodes: types.Node[] = [];

if ((types.isCallExpression(node) || types.isNewExpression(node)) && types.isIdentifier(node.callee) && node.callee.name === 'Array' && node.arguments.length > 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export default class BinaryExpressionMutator implements NodeMutator {

public name = 'BinaryExpression';

public mutate(node: types.Node, clone: <T extends types.Node> (node: T, deep?: boolean) => T): void | types.Node[] {
public mutate(node: types.Node, clone: <T extends types.Node> (node: T, deep?: boolean) => T): types.Node[] {
if (types.isBinaryExpression(node) || types.isLogicalExpression(node)) {
let mutatedOperators = this.operators[node.operator];
if (mutatedOperators) {
Expand All @@ -37,5 +37,6 @@ export default class BinaryExpressionMutator implements NodeMutator {
});
}
}
return [];
}
}
3 changes: 2 additions & 1 deletion packages/javascript-mutator/src/mutators/BlockMutator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ import { NodeMutator } from './NodeMutator';
export default class BlockMutator implements NodeMutator {
public name = 'Block';

public mutate(node: types.Node, copy: <T extends types.Node>(obj: T, deep?: boolean) => T): void | types.Node[] {
public mutate(node: types.Node, copy: <T extends types.Node>(obj: T, deep?: boolean) => T): types.Node[] {
if (types.isBlockStatement(node) && node.body.length > 0) {
const mutatedNode = copy(node);
mutatedNode.body = [];
return [mutatedNode];
}
return [];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export default class ConditionalExpressionMutator implements NodeMutator {
return this.validOperators.indexOf(operator) !== -1;
}

public mutate(node: types.Node): types.Node[] | void {
public mutate(node: types.Node): types.Node[] {
if (
(types.isBinaryExpression(node) || types.isLogicalExpression(node)) &&
this.hasValidParent(node) &&
Expand All @@ -51,5 +51,6 @@ export default class ConditionalExpressionMutator implements NodeMutator {
NodeGenerator.createBooleanLiteralNode(node, true),
];
}
return [];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ export default class DoStatementMutator implements NodeMutator {

constructor() { }

public mutate(node: types.Node): types.Node[] | void {
public mutate(node: types.Node): types.Node[] {
if (types.isDoWhileStatement(node)) {
return [NodeGenerator.createBooleanLiteralNode(node.test, false)];
return [NodeGenerator.createBooleanLiteralNode(node.test, false)];
}
return [];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export default class ForStatementMutator implements NodeMutator {

constructor() { }

public mutate(node: types.Node, copy: <T extends types.Node>(obj: T, deep?: boolean) => T): types.Node[] | void {
public mutate(node: types.Node, copy: <T extends types.Node>(obj: T, deep?: boolean) => T): types.Node[] {
if (types.isForStatement(node)) {
if (!node.test) {
const mutatedNode = copy(node) as types.ForStatement;
Expand All @@ -20,5 +20,6 @@ export default class ForStatementMutator implements NodeMutator {
return [NodeGenerator.createBooleanLiteralNode(node.test, false)];
}
}
return [];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ export default class IfStatementMutator implements NodeMutator {

constructor() { }

public mutate(node: types.Node): types.Node[] | void {
public mutate(node: types.Node): types.Node[] {
if (types.isIfStatement(node)) {
return [
NodeGenerator.createBooleanLiteralNode(node.test, false),
NodeGenerator.createBooleanLiteralNode(node.test, true)
];
}
return [];
}
}
2 changes: 1 addition & 1 deletion packages/javascript-mutator/src/mutators/NodeMutator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,5 @@ export interface NodeMutator {
* @param copy A function to create a copy of an object.
* @returns An array of mutated Nodes.
*/
mutate(node: NodeWithParent, copy: <T extends types.Node> (obj: T, deep?: boolean) => T): void | types.Node[];
mutate(node: NodeWithParent, copy: <T extends types.Node> (obj: T, deep?: boolean) => T): types.Node[];
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ import { NodeMutator } from './NodeMutator';
export default class ObjectLiteralMutator implements NodeMutator {
public name = 'ObjectLiteral';

public mutate(node: types.Node, copy: <T extends types.Node>(obj: T, deep?: boolean) => T): void | types.Node[] {
public mutate(node: types.Node, copy: <T extends types.Node>(obj: T, deep?: boolean) => T): types.Node[] {
if (types.isObjectExpression(node) && node.properties.length > 0) {
const mutatedNode = copy(node);
mutatedNode.properties = [];
return [mutatedNode];
}
return [];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ export default class PostfixUnaryExpressionMutator implements NodeMutator {
'--': '++'
};

public mutate(node: types.Node, copy: <T extends types.Node>(obj: T, deep?: boolean) => T): void | types.Node[] {
public mutate(node: types.Node, copy: <T extends types.Node>(obj: T, deep?: boolean) => T): types.Node[] {
if (types.isUpdateExpression(node) && !node.prefix && this.operators[node.operator]) {
const mutatedNode = copy(node);
mutatedNode.operator = this.operators[node.operator] as any;
return [mutatedNode];
}
return [];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export default class PrefixUnaryExpressionMutator implements NodeMutator {
'~': ''
};

public mutate(node: types.Node, copy: <T extends types.Node>(obj: T, deep?: boolean) => T): void | types.Node[] {
public mutate(node: types.Node, copy: <T extends types.Node>(obj: T, deep?: boolean) => T): types.Node[] {
if ((types.isUpdateExpression(node) || types.isUnaryExpression(node)) && this.operators[node.operator] !== undefined && node.prefix) {
if (this.operators[node.operator].length > 0) {
const mutatedNode = copy(node);
Expand All @@ -25,5 +25,6 @@ export default class PrefixUnaryExpressionMutator implements NodeMutator {
return [mutatedNode];
}
}
return [];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { NodeWithParent } from '../helpers/ParentNode';
export default class SwitchCaseMutator implements NodeMutator {
public name = 'SwitchCase';

public mutate(node: NodeWithParent, copy: <T extends types.Node> (obj: T, deep?: boolean) => T): void | types.Node[] {
public mutate(node: NodeWithParent, copy: <T extends types.Node> (obj: T, deep?: boolean) => T): types.Node[] {
if (types.isSwitchCase(node)) {
// if not a fallthrough case
if (node.consequent.length > 0) {
Expand All @@ -17,5 +17,6 @@ export default class SwitchCaseMutator implements NodeMutator {
return [mutatedNode];
}
}
return [];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ export default class WhileStatementMutator implements NodeMutator {

constructor() { }

public mutate(node: types.Node): types.Node[] | void {
public mutate(node: types.Node): types.Node[] {
if (types.isWhileStatement(node)) {
return [NodeGenerator.createBooleanLiteralNode(node.test, false)];
}
return [];
}
}
94 changes: 94 additions & 0 deletions packages/javascript-mutator/test/unit/JavaScriptMutator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,4 +164,98 @@ describe('JavaScriptMutator', () => {
const mutants = sut.mutate(files);
expect(mutants).lengthOf.above(0);
});

it('should disable mutations after using `stryker:off`', () => {
const sut = createSut();
const files: File[] = [new File('testFile.js', `
// stryker:off
function hello() {
return 2 + 1 - 3;
}
`)];

const mutants = sut.mutate(files);
expect(mutants.length).equal(0);
});

it('should should change mutations for multiple comments', () => {
const sut = createSut();
const files: File[] = [new File('testFile.js', `
// stryker will be disabled now
// stryker:off
function hello() {
return 2 + 1 - 3;
}
`)];

const mutants = sut.mutate(files);
expect(mutants.length).equal(0);
});

it('should should enable mutations using `stryker:on`', () => {
const sut = createSut();
const files: File[] = [new File('testFile.js', `
// stryker:off
// stryker:on
function hello() {
return 2 + 1 - 3;
}
`)];

const mutants = sut.mutate(files);
expect(mutants.length).equal(3);
});
it('should should disable specific mutations using `stryker:off mutatorName`', () => {
const sut = createSut();
const files: File[] = [new File('testFile.js', `
// stryker:off BinaryExpression, Block
function hello() {
return 2 + 1 - 3;
}
`)];

const mutants = sut.mutate(files);
expect(mutants.length).equal(0);
});

it('should should enable specific mutations using `stryker:on mutatorName`', () => {
const sut = createSut();
const files: File[] = [new File('testFile.js', `
// stryker:off
// stryker:on BinaryExpression
function hello() {
return 2 + 1 - 3;
}
`)];

const mutants = sut.mutate(files);
expect(mutants.length).equal(2);
});

it('should should enable one mutations using `stryker:next-on`', () => {
const sut = createSut();
const files: File[] = [new File('testFile.js', `
// stryker:off
// stryker:next-on
function hello() {
return 2 + 1 - 3;
}
`)];

const mutants = sut.mutate(files);
expect(mutants.length).equal(1);
});

it('should should disable one mutations using `stryker:next-off`', () => {
const sut = createSut();
const files: File[] = [new File('testFile.js', `
// stryker:next-off
function hello() {
return 2 + 1 - 3;
}
`)];

const mutants = sut.mutate(files);
expect(mutants.length).equal(2);
});
});
3 changes: 2 additions & 1 deletion packages/typescript/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@
"@stryker-mutator/util": "^2.0.2",
"lodash.flatmap": "~4.5.0",
"semver": "~6.2.0",
"tslib": "~1.10.0"
"tslib": "~1.10.0",
"tsutils": "^3.14.1"
},
"devDependencies": {
"@stryker-mutator/mutator-specification": "^2.0.2",
Expand Down
Loading