From 0902d23162b3ab3e966ea1219eb17e1012119e24 Mon Sep 17 00:00:00 2001 From: Leoniak Bartosz Date: Thu, 25 Jul 2019 11:38:04 +0200 Subject: [PATCH 1/5] feat(#1472): add excluding specific mutations --- .../src/JavaScriptMutator.ts | 60 ++++++++++++++--- .../src/mutators/ArrayLiteralMutator.ts | 2 +- .../src/mutators/ArrayNewExpressionMutator.ts | 2 +- .../src/mutators/BinaryExpressionMutator.ts | 3 +- .../src/mutators/BlockMutator.ts | 3 +- .../mutators/ConditionalExpressionMutator.ts | 3 +- .../src/mutators/DoStatementMutator.ts | 5 +- .../src/mutators/ForStatementMutator.ts | 3 +- .../src/mutators/IfStatementMutator.ts | 3 +- .../src/mutators/NodeMutator.ts | 2 +- .../src/mutators/ObjectLiteralMutator.ts | 3 +- .../mutators/PostfixUnaryExpressionMutator.ts | 3 +- .../mutators/PrefixUnaryExpressionMutator.ts | 3 +- .../src/mutators/SwitchCaseMutator.ts | 3 +- .../src/mutators/WhileStatementMutator.ts | 3 +- .../test/unit/JavaScriptMutator.spec.ts | 67 +++++++++++++++++++ 16 files changed, 145 insertions(+), 23 deletions(-) diff --git a/packages/javascript-mutator/src/JavaScriptMutator.ts b/packages/javascript-mutator/src/JavaScriptMutator.ts index 34f1970358..9d20f8f76c 100644 --- a/packages/javascript-mutator/src/JavaScriptMutator.ts +++ b/packages/javascript-mutator/src/JavaScriptMutator.ts @@ -8,12 +8,52 @@ import BabelHelper from './helpers/BabelHelper'; import { tokens, commonTokens } from '@stryker-mutator/api/plugin'; export class JavaScriptMutator implements Mutator { + private readonly mutators: ReadonlyArray; + private readonly mutatorsSwitch: { [key: string]: boolean }; public static inject = tokens(commonTokens.logger, NODE_MUTATORS_TOKEN) ; - constructor( - private readonly log: Logger, - private readonly mutators: ReadonlyArray - ) { } + constructor(private readonly log: Logger, mutators: ReadonlyArray) { + this.mutators = mutators; + 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) { + 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; + } + }); + } public mutate(inputFiles: File[]): Mutant[] { const mutants: Mutant[] = []; @@ -22,12 +62,16 @@ 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) { + const mutatedNodes = mutator.mutate(node, copy); - if (mutatedNodes) { - const newMutants = this.generateMutants(mutatedNodes, mutator.name, file.name); - mutants.push(...newMutants); + if (mutatedNodes.length > 0) { + const newMutants = this.generateMutants(mutatedNodes, mutator.name, file.name); + mutants.push(...newMutants); + } } }); }); diff --git a/packages/javascript-mutator/src/mutators/ArrayLiteralMutator.ts b/packages/javascript-mutator/src/mutators/ArrayLiteralMutator.ts index 6ade8b4b93..192cde7658 100644 --- a/packages/javascript-mutator/src/mutators/ArrayLiteralMutator.ts +++ b/packages/javascript-mutator/src/mutators/ArrayLiteralMutator.ts @@ -7,7 +7,7 @@ import { NodeMutator } from './NodeMutator'; export default class ArrayLiteralMutator implements NodeMutator { public name = 'ArrayLiteral'; - public mutate(node: types.Node, copy: (obj: T, deep?: boolean) => T): void | types.Node[] { + public mutate(node: types.Node, copy: (obj: T, deep?: boolean) => T): types.Node[] { const nodes: types.Node[] = []; if (types.isArrayExpression(node) && node.elements.length > 0) { diff --git a/packages/javascript-mutator/src/mutators/ArrayNewExpressionMutator.ts b/packages/javascript-mutator/src/mutators/ArrayNewExpressionMutator.ts index 5122f14be0..ecc9d090ef 100644 --- a/packages/javascript-mutator/src/mutators/ArrayNewExpressionMutator.ts +++ b/packages/javascript-mutator/src/mutators/ArrayNewExpressionMutator.ts @@ -7,7 +7,7 @@ import { NodeMutator } from './NodeMutator'; export default class ArrayNewExpressionMutator implements NodeMutator { public name = 'ArrayNewExpression'; - public mutate(node: types.Node, copy: (obj: T, deep?: boolean) => T): void | types.Node[] { + public mutate(node: types.Node, copy: (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) { diff --git a/packages/javascript-mutator/src/mutators/BinaryExpressionMutator.ts b/packages/javascript-mutator/src/mutators/BinaryExpressionMutator.ts index 0a910d682e..545bdbdf08 100644 --- a/packages/javascript-mutator/src/mutators/BinaryExpressionMutator.ts +++ b/packages/javascript-mutator/src/mutators/BinaryExpressionMutator.ts @@ -22,7 +22,7 @@ export default class BinaryExpressionMutator implements NodeMutator { public name = 'BinaryExpression'; - public mutate(node: types.Node, clone: (node: T, deep?: boolean) => T): void | types.Node[] { + public mutate(node: types.Node, clone: (node: T, deep?: boolean) => T): types.Node[] { if (types.isBinaryExpression(node) || types.isLogicalExpression(node)) { let mutatedOperators = this.operators[node.operator]; if (mutatedOperators) { @@ -37,5 +37,6 @@ export default class BinaryExpressionMutator implements NodeMutator { }); } } + return []; } } diff --git a/packages/javascript-mutator/src/mutators/BlockMutator.ts b/packages/javascript-mutator/src/mutators/BlockMutator.ts index 78e6ed99eb..4d028667fa 100644 --- a/packages/javascript-mutator/src/mutators/BlockMutator.ts +++ b/packages/javascript-mutator/src/mutators/BlockMutator.ts @@ -7,11 +7,12 @@ import { NodeMutator } from './NodeMutator'; export default class BlockMutator implements NodeMutator { public name = 'Block'; - public mutate(node: types.Node, copy: (obj: T, deep?: boolean) => T): void | types.Node[] { + public mutate(node: types.Node, copy: (obj: T, deep?: boolean) => T): types.Node[] { if (types.isBlockStatement(node) && node.body.length > 0) { const mutatedNode = copy(node); mutatedNode.body = []; return [mutatedNode]; } + return []; } } diff --git a/packages/javascript-mutator/src/mutators/ConditionalExpressionMutator.ts b/packages/javascript-mutator/src/mutators/ConditionalExpressionMutator.ts index a8093466bc..b177502378 100644 --- a/packages/javascript-mutator/src/mutators/ConditionalExpressionMutator.ts +++ b/packages/javascript-mutator/src/mutators/ConditionalExpressionMutator.ts @@ -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) && @@ -51,5 +51,6 @@ export default class ConditionalExpressionMutator implements NodeMutator { NodeGenerator.createBooleanLiteralNode(node, true), ]; } + return []; } } diff --git a/packages/javascript-mutator/src/mutators/DoStatementMutator.ts b/packages/javascript-mutator/src/mutators/DoStatementMutator.ts index 1689c4fd50..e85e9901d8 100644 --- a/packages/javascript-mutator/src/mutators/DoStatementMutator.ts +++ b/packages/javascript-mutator/src/mutators/DoStatementMutator.ts @@ -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 []; } } diff --git a/packages/javascript-mutator/src/mutators/ForStatementMutator.ts b/packages/javascript-mutator/src/mutators/ForStatementMutator.ts index 367e1bcd13..23544460e1 100644 --- a/packages/javascript-mutator/src/mutators/ForStatementMutator.ts +++ b/packages/javascript-mutator/src/mutators/ForStatementMutator.ts @@ -10,7 +10,7 @@ export default class ForStatementMutator implements NodeMutator { constructor() { } - public mutate(node: types.Node, copy: (obj: T, deep?: boolean) => T): types.Node[] | void { + public mutate(node: types.Node, copy: (obj: T, deep?: boolean) => T): types.Node[] { if (types.isForStatement(node)) { if (!node.test) { const mutatedNode = copy(node) as types.ForStatement; @@ -20,5 +20,6 @@ export default class ForStatementMutator implements NodeMutator { return [NodeGenerator.createBooleanLiteralNode(node.test, false)]; } } + return []; } } diff --git a/packages/javascript-mutator/src/mutators/IfStatementMutator.ts b/packages/javascript-mutator/src/mutators/IfStatementMutator.ts index 3ca5e7cb4c..0512a87681 100644 --- a/packages/javascript-mutator/src/mutators/IfStatementMutator.ts +++ b/packages/javascript-mutator/src/mutators/IfStatementMutator.ts @@ -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 []; } } diff --git a/packages/javascript-mutator/src/mutators/NodeMutator.ts b/packages/javascript-mutator/src/mutators/NodeMutator.ts index 4ca795f9a7..1afdc9ae0b 100644 --- a/packages/javascript-mutator/src/mutators/NodeMutator.ts +++ b/packages/javascript-mutator/src/mutators/NodeMutator.ts @@ -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: (obj: T, deep?: boolean) => T): void | types.Node[]; + mutate(node: NodeWithParent, copy: (obj: T, deep?: boolean) => T): types.Node[]; } diff --git a/packages/javascript-mutator/src/mutators/ObjectLiteralMutator.ts b/packages/javascript-mutator/src/mutators/ObjectLiteralMutator.ts index 46a976f5ee..6b530e2b50 100644 --- a/packages/javascript-mutator/src/mutators/ObjectLiteralMutator.ts +++ b/packages/javascript-mutator/src/mutators/ObjectLiteralMutator.ts @@ -7,11 +7,12 @@ import { NodeMutator } from './NodeMutator'; export default class ObjectLiteralMutator implements NodeMutator { public name = 'ObjectLiteral'; - public mutate(node: types.Node, copy: (obj: T, deep?: boolean) => T): void | types.Node[] { + public mutate(node: types.Node, copy: (obj: T, deep?: boolean) => T): types.Node[] { if (types.isObjectExpression(node) && node.properties.length > 0) { const mutatedNode = copy(node); mutatedNode.properties = []; return [mutatedNode]; } + return []; } } diff --git a/packages/javascript-mutator/src/mutators/PostfixUnaryExpressionMutator.ts b/packages/javascript-mutator/src/mutators/PostfixUnaryExpressionMutator.ts index 33343fd9e1..2131d2eab5 100644 --- a/packages/javascript-mutator/src/mutators/PostfixUnaryExpressionMutator.ts +++ b/packages/javascript-mutator/src/mutators/PostfixUnaryExpressionMutator.ts @@ -9,11 +9,12 @@ export default class PostfixUnaryExpressionMutator implements NodeMutator { '--': '++' }; - public mutate(node: types.Node, copy: (obj: T, deep?: boolean) => T): void | types.Node[] { + public mutate(node: types.Node, copy: (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 []; } } diff --git a/packages/javascript-mutator/src/mutators/PrefixUnaryExpressionMutator.ts b/packages/javascript-mutator/src/mutators/PrefixUnaryExpressionMutator.ts index 88c411f060..7c84878144 100644 --- a/packages/javascript-mutator/src/mutators/PrefixUnaryExpressionMutator.ts +++ b/packages/javascript-mutator/src/mutators/PrefixUnaryExpressionMutator.ts @@ -13,7 +13,7 @@ export default class PrefixUnaryExpressionMutator implements NodeMutator { '~': '' }; - public mutate(node: types.Node, copy: (obj: T, deep?: boolean) => T): void | types.Node[] { + public mutate(node: types.Node, copy: (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); @@ -25,5 +25,6 @@ export default class PrefixUnaryExpressionMutator implements NodeMutator { return [mutatedNode]; } } + return []; } } diff --git a/packages/javascript-mutator/src/mutators/SwitchCaseMutator.ts b/packages/javascript-mutator/src/mutators/SwitchCaseMutator.ts index c7a96d9bf9..2aac6eff14 100644 --- a/packages/javascript-mutator/src/mutators/SwitchCaseMutator.ts +++ b/packages/javascript-mutator/src/mutators/SwitchCaseMutator.ts @@ -8,7 +8,7 @@ import { NodeWithParent } from '../helpers/ParentNode'; export default class SwitchCaseMutator implements NodeMutator { public name = 'SwitchCase'; - public mutate(node: NodeWithParent, copy: (obj: T, deep?: boolean) => T): void | types.Node[] { + public mutate(node: NodeWithParent, copy: (obj: T, deep?: boolean) => T): types.Node[] { if (types.isSwitchCase(node)) { // if not a fallthrough case if (node.consequent.length > 0) { @@ -17,5 +17,6 @@ export default class SwitchCaseMutator implements NodeMutator { return [mutatedNode]; } } + return []; } } diff --git a/packages/javascript-mutator/src/mutators/WhileStatementMutator.ts b/packages/javascript-mutator/src/mutators/WhileStatementMutator.ts index 48c8a8b1ac..e0c1b7a82a 100644 --- a/packages/javascript-mutator/src/mutators/WhileStatementMutator.ts +++ b/packages/javascript-mutator/src/mutators/WhileStatementMutator.ts @@ -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 []; } } diff --git a/packages/javascript-mutator/test/unit/JavaScriptMutator.spec.ts b/packages/javascript-mutator/test/unit/JavaScriptMutator.spec.ts index 6d1202e896..a0696974bb 100644 --- a/packages/javascript-mutator/test/unit/JavaScriptMutator.spec.ts +++ b/packages/javascript-mutator/test/unit/JavaScriptMutator.spec.ts @@ -164,4 +164,71 @@ 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); + }); }); From 46b90822efc1397e68367e39be8591ca2327c47b Mon Sep 17 00:00:00 2001 From: Leoniak Bartosz Date: Fri, 26 Jul 2019 08:39:21 +0200 Subject: [PATCH 2/5] feat(#1472): add next-on/off implementation --- .../src/JavaScriptMutator.ts | 17 +++++++++--- .../test/unit/JavaScriptMutator.spec.ts | 27 +++++++++++++++++++ 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/packages/javascript-mutator/src/JavaScriptMutator.ts b/packages/javascript-mutator/src/JavaScriptMutator.ts index 9d20f8f76c..3bfbcd8859 100644 --- a/packages/javascript-mutator/src/JavaScriptMutator.ts +++ b/packages/javascript-mutator/src/JavaScriptMutator.ts @@ -10,10 +10,12 @@ import { tokens, commonTokens } from '@stryker-mutator/api/plugin'; export class JavaScriptMutator implements Mutator { private readonly mutators: ReadonlyArray; 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, mutators: ReadonlyArray) { this.mutators = mutators; + this.nextMutation = 0; this.mutatorsSwitch = {}; mutators.forEach(mutator => { this.mutatorsSwitch[mutator.name] = true; @@ -51,6 +53,11 @@ export class JavaScriptMutator implements Mutator { 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; } }); } @@ -63,14 +70,16 @@ export class JavaScriptMutator implements Mutator { BabelHelper.getNodes(ast).forEach(node => { this.mutatorsSwitcher(node.leadingComments || []); - this.mutators.forEach(mutator => { - if (this.mutatorsSwitch[mutator.name] === true) { + if (this.mutatorsSwitch[mutator.name] === true || this.nextMutation === 1) { const mutatedNodes = mutator.mutate(node, copy); if (mutatedNodes.length > 0) { - const newMutants = this.generateMutants(mutatedNodes, mutator.name, file.name); - mutants.push(...newMutants); + if (this.nextMutation !== -1) { + const newMutants = this.generateMutants(mutatedNodes, mutator.name, file.name); + mutants.push(...newMutants); + } + this.nextMutation = 0; } } }); diff --git a/packages/javascript-mutator/test/unit/JavaScriptMutator.spec.ts b/packages/javascript-mutator/test/unit/JavaScriptMutator.spec.ts index a0696974bb..491efd2de0 100644 --- a/packages/javascript-mutator/test/unit/JavaScriptMutator.spec.ts +++ b/packages/javascript-mutator/test/unit/JavaScriptMutator.spec.ts @@ -231,4 +231,31 @@ describe('JavaScriptMutator', () => { 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); + }); }); From 9f506c2373dfd896699f8878b96d279b213e464e Mon Sep 17 00:00:00 2001 From: Leoniak Bartosz Date: Thu, 1 Aug 2019 11:43:00 +0200 Subject: [PATCH 3/5] feat: add stryker:options to TypeScript --- packages/typescript/package.json | 3 +- packages/typescript/src/TypescriptMutator.ts | 74 ++++++++++++++- .../test/unit/TypescriptMutator.spec.ts | 95 +++++++++++++++++++ 3 files changed, 168 insertions(+), 4 deletions(-) diff --git a/packages/typescript/package.json b/packages/typescript/package.json index 74801c5146..624c117ad5 100644 --- a/packages/typescript/package.json +++ b/packages/typescript/package.json @@ -40,7 +40,8 @@ "@stryker-mutator/util": "^2.0.0", "lodash.flatmap": "~4.5.0", "semver": "~6.1.0", - "tslib": "~1.10.0" + "tslib": "~1.10.0", + "tsutils": "^3.14.1" }, "devDependencies": { "@stryker-mutator/mutator-specification": "^2.0.0", diff --git a/packages/typescript/src/TypescriptMutator.ts b/packages/typescript/src/TypescriptMutator.ts index c60ea64761..b647007589 100644 --- a/packages/typescript/src/TypescriptMutator.ts +++ b/packages/typescript/src/TypescriptMutator.ts @@ -6,6 +6,7 @@ import { parseFile, getTSConfig } from './helpers/tsHelpers'; import NodeMutator from './mutator/NodeMutator'; import { commonTokens, tokens, Injector, OptionsContext } from '@stryker-mutator/api/plugin'; import { nodeMutators } from './mutator'; +import { forEachComment } from 'tsutils'; export function typescriptMutatorFactory(injector: Injector): TypescriptMutator { return injector @@ -17,8 +18,20 @@ typescriptMutatorFactory.inject = tokens(commonTokens.injector); export const MUTATORS_TOKEN = 'mutators'; export class TypescriptMutator { + public mutators: ReadonlyArray; + private readonly mutatorsSwitch: { [key: string]: boolean }; + private nextMutation = -1 | 0 | 1; + public static inject = tokens(commonTokens.options, MUTATORS_TOKEN); - constructor(private readonly options: StrykerOptions, public mutators: ReadonlyArray) { } + + constructor(private readonly options: StrykerOptions, mutators: ReadonlyArray) { + this.mutators = mutators; + this.nextMutation = 0; + this.mutatorsSwitch = {}; + mutators.forEach(mutator => { + this.mutatorsSwitch[mutator.name] = true; + }); + } public mutate(inputFiles: File[]): Mutant[] { const tsConfig = getTSConfig(this.options); @@ -29,15 +42,70 @@ export class TypescriptMutator { return mutants; } + private parseSwitchers(data: string) { + return data.split(',').map(el => el.trim()).filter(el => el !== ''); + } + + private updateMutators(data: string[], newValue: boolean) { + 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(comment: string) { + const regexp = /\/[/*]\s*stryker:(next-)?(on|off)((?:\s\w+,\s?)*(?:\s\w+)?)\s*(?:$|\*\/)/; + const ex = regexp.exec(comment); + if (ex === null) { + return; + } + if ( ex[1] === 'next-') { + this.nextMutation = ex[2] === 'on' ? 1 : -1; + } else { + if (ex[2] === 'on') { + this.updateMutators(this.parseSwitchers(ex[3]), true); + } else { + this.updateMutators(this.parseSwitchers(ex[3]), false); + } + } + } + private mutateForNode(node: T, sourceFile: ts.SourceFile): Mutant[] { if (shouldNodeBeSkipped(node)) { return []; } else { + forEachComment(node, (c, com) => { + this.mutatorsSwitcher(sourceFile.getFullText().substring(com.pos, com.end)); + }); const targetMutators = this.mutators.filter(mutator => mutator.guard(node)); - const mutants = flatMap(targetMutators, mutator => mutator.mutate(node, sourceFile)); + console.log(this.nextMutation, this.mutatorsSwitch); + const mutants = flatMap(targetMutators, mutator => { + if (this.mutatorsSwitch[mutator.name] || this.nextMutation === 1) { + const mutatedNodes = mutator.mutate(node, sourceFile); + + if (mutatedNodes.length > 0) { + if (this.nextMutation !== -1) { + this.nextMutation = 0; + return mutatedNodes; + } + this.nextMutation = 0; + return []; + } + return []; + } + return []; + }); + node.forEachChild(child => { // It is important that forEachChild does not return a true, otherwise node visiting is halted! - mutants.push(... this.mutateForNode(child, sourceFile)); + mutants.push(...this.mutateForNode(child, sourceFile)); }); return mutants; } diff --git a/packages/typescript/test/unit/TypescriptMutator.spec.ts b/packages/typescript/test/unit/TypescriptMutator.spec.ts index 7c1547d8c0..310513bb93 100644 --- a/packages/typescript/test/unit/TypescriptMutator.spec.ts +++ b/packages/typescript/test/unit/TypescriptMutator.spec.ts @@ -150,6 +150,101 @@ describe('TypescriptMutator', () => { } ]); }); + + 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 SourceFileForTest, FunctionDeclarationForTest + 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 SourceFileForTest + 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); + console.log(mutants); + 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); + }); }); }); From d1f5464b7801e09e88399bc544ec832cf101b099 Mon Sep 17 00:00:00 2001 From: Leoniak Bartosz Date: Thu, 1 Aug 2019 11:48:53 +0200 Subject: [PATCH 4/5] fix: issue with test --- packages/typescript/test/unit/TypescriptMutator.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/typescript/test/unit/TypescriptMutator.spec.ts b/packages/typescript/test/unit/TypescriptMutator.spec.ts index 310513bb93..bd44a0e27c 100644 --- a/packages/typescript/test/unit/TypescriptMutator.spec.ts +++ b/packages/typescript/test/unit/TypescriptMutator.spec.ts @@ -209,7 +209,7 @@ describe('TypescriptMutator', () => { const sut = createSut(); const files: File[] = [new File('testFile.js', ` // stryker:off - // stryker:on SourceFileForTest + // stryker:on FunctionDeclarationForTest function hello() { return 2 + 1 - 3; } From 99ae0a36af8b896eeeebe54c57a297adac0f6268 Mon Sep 17 00:00:00 2001 From: Leoniak Bartosz Date: Thu, 1 Aug 2019 11:49:36 +0200 Subject: [PATCH 5/5] maintain: clean up logs --- packages/typescript/src/TypescriptMutator.ts | 1 - packages/typescript/test/unit/TypescriptMutator.spec.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/typescript/src/TypescriptMutator.ts b/packages/typescript/src/TypescriptMutator.ts index b647007589..57afd1229f 100644 --- a/packages/typescript/src/TypescriptMutator.ts +++ b/packages/typescript/src/TypescriptMutator.ts @@ -85,7 +85,6 @@ export class TypescriptMutator { this.mutatorsSwitcher(sourceFile.getFullText().substring(com.pos, com.end)); }); const targetMutators = this.mutators.filter(mutator => mutator.guard(node)); - console.log(this.nextMutation, this.mutatorsSwitch); const mutants = flatMap(targetMutators, mutator => { if (this.mutatorsSwitch[mutator.name] || this.nextMutation === 1) { const mutatedNodes = mutator.mutate(node, sourceFile); diff --git a/packages/typescript/test/unit/TypescriptMutator.spec.ts b/packages/typescript/test/unit/TypescriptMutator.spec.ts index bd44a0e27c..25b807828c 100644 --- a/packages/typescript/test/unit/TypescriptMutator.spec.ts +++ b/packages/typescript/test/unit/TypescriptMutator.spec.ts @@ -229,7 +229,6 @@ describe('TypescriptMutator', () => { } `)]; const mutants = sut.mutate(files); - console.log(mutants); expect(mutants.length).equal(1); });