diff --git a/package.json b/package.json index fd2dcccef8..9e91b12fbc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "private": true, "devDependencies": { + "@types/babel-core": "^6.25.2", "@types/chai-as-promised": "0.0.31", "@types/chalk": "^0.4.28", "@types/commander": "^2.9.0", diff --git a/packages/stryker-javascript-mutator/.npmignore b/packages/stryker-javascript-mutator/.npmignore new file mode 100644 index 0000000000..d5f6bc8cab --- /dev/null +++ b/packages/stryker-javascript-mutator/.npmignore @@ -0,0 +1,10 @@ +**/* +!*.d.ts +!bin/** +!src/** +src/**/*.map +src/**/*.ts +!src/**/*.d.ts +!readme.md +!LICENSE +!CHANGELOG.md \ No newline at end of file diff --git a/packages/stryker-javascript-mutator/.vscode/launch.json b/packages/stryker-javascript-mutator/.vscode/launch.json new file mode 100644 index 0000000000..e84cf225d1 --- /dev/null +++ b/packages/stryker-javascript-mutator/.vscode/launch.json @@ -0,0 +1,36 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Unit tests", + "program": "${workspaceRoot}/../../node_modules/mocha/bin/_mocha", + "args": [ + "-u", + "tdd", + "--timeout", + "999999", + "--colors", + "${workspaceRoot}/test/helpers/**/*.js", + "${workspaceRoot}/test/unit/**/*.js" + ], + "internalConsoleOptions": "openOnSessionStart" + }, { + "type": "node", + "request": "launch", + "name": "Integration tests", + "program": "${workspaceRoot}/../../node_modules/mocha/bin/_mocha", + "args": [ + "-u", + "tdd", + "--timeout", + "999999", + "--colors", + "${workspaceRoot}/test/helpers/**/*.js", + "${workspaceRoot}/test/integration/**/*.js" + ], + "internalConsoleOptions": "openOnSessionStart" + } + ] +} \ No newline at end of file diff --git a/packages/stryker-javascript-mutator/.vscode/settings.json b/packages/stryker-javascript-mutator/.vscode/settings.json new file mode 100644 index 0000000000..3a180b1e69 --- /dev/null +++ b/packages/stryker-javascript-mutator/.vscode/settings.json @@ -0,0 +1,14 @@ +{ + "typescript.tsdk": "../../node_modules/typescript/lib", + "files.exclude": { + ".git": true, + ".tscache": true, + "**/*.js": { + "when": "$(basename).ts" + }, + "**/*.d.ts": true, + "**/*.map": { + "when": "$(basename)" + } + } +} \ No newline at end of file diff --git a/packages/stryker-javascript-mutator/.vscode/tasks.json b/packages/stryker-javascript-mutator/.vscode/tasks.json new file mode 100644 index 0000000000..18cf4c8a6e --- /dev/null +++ b/packages/stryker-javascript-mutator/.vscode/tasks.json @@ -0,0 +1,18 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "taskName": "tsc-watch", + "type": "shell", + "command": "npm start", + "problemMatcher": "$tsc-watch", + "isBackground": true, + "group": { + "kind": "build", + "isDefault": true + } + } + ] +} \ No newline at end of file diff --git a/packages/stryker-javascript-mutator/README.md b/packages/stryker-javascript-mutator/README.md new file mode 100644 index 0000000000..c17cb71e93 --- /dev/null +++ b/packages/stryker-javascript-mutator/README.md @@ -0,0 +1,39 @@ +[![Build Status](https://travis-ci.org/stryker-mutator/stryker.svg?branch=master)](https://travis-ci.org/stryker-mutator/stryker) +[![NPM](https://img.shields.io/npm/dm/stryker-javascript-mutator.svg)](https://www.npmjs.com/package/stryker-javascript-mutator) +[![Node version](https://img.shields.io/node/v/stryker-javascript-mutator.svg)](https://img.shields.io/node/v/stryker-javascript-mutator.svg) +[![Gitter](https://badges.gitter.im/stryker-mutator/stryker.svg)](https://gitter.im/stryker-mutator/stryker?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) +[![BCH compliance](https://bettercodehub.com/edge/badge/stryker-mutator/stryker)](https://bettercodehub.com/) + +![Stryker](https://github.com/stryker-mutator/stryker/raw/master/stryker-80x80.png) + +# Stryker JavaScript mutator + +A mutator that supports JavaScript for [Stryker](https://stryker-mutator.github.io), the JavaScript Mutation testing framework. This plugin does not transpile any code. The code that the stryker-javascript-mutator gets should be executable in your environment (i.e. the stryker-javascript-mutator does not add support for Babel projects). + +## Quickstart + +First, install Stryker itself (you can follow the [quickstart on the website](http://stryker-mutator.github.io/quickstart.html)) + +Next, install this package: + +```bash +npm install --save-dev stryker-javascript-mutator +``` + +Now open up your stryker.conf.js file and add the following components: + +```javascript +mutator: 'javascript', +``` + +Now give it a go: + +```bash +$ stryker run +``` + +### JavaScript Mutator + +The `JavaScript Mutator` is a plugin to mutate JavaScript code. This is done using Babel without any plugins. + +See [test code](https://github.com/stryker-mutator/stryker/tree/master/packages/stryker-javascript-mutator/test/unit/mutator) to know which mutations are supported. \ No newline at end of file diff --git a/packages/stryker-javascript-mutator/package.json b/packages/stryker-javascript-mutator/package.json new file mode 100644 index 0000000000..b95bbcfe02 --- /dev/null +++ b/packages/stryker-javascript-mutator/package.json @@ -0,0 +1,56 @@ +{ + "name": "stryker-javascript-mutator", + "version": "0.1.0", + "description": "A plugin for javascript projects using Stryker", + "main": "src/index.js", + "scripts": { + "start": "tsc -w", + "clean": "rimraf \"+(test|src)/**/*+(.d.ts|.js|.map)\" reports", + "prebuild": "npm run clean", + "build": "tsc -p .", + "postbuild": "tslint -p tsconfig.json", + "test": "nyc --reporter=html --report-dir=reports/coverage --check-coverage --lines 85 --functions 90 --branches 60 mocha \"test/helpers/**/*.js\" \"test/unit/**/*.js\" " + }, + "repository": { + "type": "git", + "url": "https://github.com/stryker-mutator/stryker" + }, + "engines": { + "node": ">=4" + }, + "keywords": [ + "stryker", + "stryker-plugin", + "javascript", + "stryker-mutator" + ], + "bugs": { + "url": "https://github.com/stryker-mutator/stryker/issues" + }, + "author": "Simon de Lang ", + "contributors": [ + "Nico Jansen ", + "Niek te Grootenhuis ", + "Thomas Peters ", + "Sander Koenders " + ], + "homepage": "https://github.com/stryker-mutator/stryker/tree/master/packages/stryker-javascript-mutator#readme", + "license": "Apache-2.0", + "dependencies": { + "babel-core": "^6.26.0", + "babel-generator": "^6.26.0", + "babylon": "^6.18.0", + "log4js": "^1.1.1", + "tslib": "^1.8.0" + }, + "devDependencies": { + "@types/babel-generator": "^6.25.1", + "@types/babylon": "^6.16.2", + "stryker-api": "^0.11.0", + "stryker-mutator-specification": "^0.1.0" + }, + "peerDependencies": { + "stryker-api": "^0.11.0", + "typescript": "^2.5.3" + } +} diff --git a/packages/stryker-javascript-mutator/src/JavaScriptMutator.ts b/packages/stryker-javascript-mutator/src/JavaScriptMutator.ts new file mode 100644 index 0000000000..897bc9c904 --- /dev/null +++ b/packages/stryker-javascript-mutator/src/JavaScriptMutator.ts @@ -0,0 +1,65 @@ +import * as babel from 'babel-core'; +import { getLogger } from 'log4js'; +import { Mutator, Mutant } from 'stryker-api/mutant'; +import { File, FileKind, TextFile } from 'stryker-api/core'; +import { Config } from 'stryker-api/config'; +import BabelParser from './helpers/BabelParser'; +import copy from './helpers/copy'; +import NodeMutatorFactory from './NodeMutatorFactory'; +import NodeMutator from './mutators/NodeMutator'; + +function defaultMutators(): NodeMutator[] { + return NodeMutatorFactory.instance().knownNames().map(name => NodeMutatorFactory.instance().create(name, undefined)); +} + +export default class JavaScriptMutator implements Mutator { + private log = getLogger(JavaScriptMutator.name); + + constructor(config: Config, private mutators: NodeMutator[] = defaultMutators()) { + } + + public mutate(inputFiles: File[]): Mutant[] { + const mutants: Mutant[] = []; + + inputFiles.filter(i => i.kind === FileKind.Text && i.mutated).forEach((file: TextFile) => { + const ast = BabelParser.getAst(file.content); + const baseAst = copy(ast, true); + BabelParser.removeUseStrict(baseAst); + + BabelParser.getNodes(ast).forEach(node => { + this.mutators.forEach(mutator => { + let mutatedNodes = mutator.mutate(node, copy); + + if (mutatedNodes) { + const newMutants = this.generateMutants(mutatedNodes, baseAst, file, mutator.name); + newMutants.forEach(mutant => mutants.push(mutant)); + } + }); + }); + }); + + return mutants; + } + + private generateMutants(nodes: babel.types.Node[], ast: babel.types.File, file: TextFile, mutatorName: string) { + const mutants: Mutant[] = []; + + nodes.forEach(node => { + const replacement = BabelParser.generateCode(ast, node); + if (replacement) { + const range: [number, number] = [node.start, node.end]; + + const mutant = { + mutatorName, + fileName: file.name, + range, + replacement + }; + this.log.trace(`Generated mutant for mutator ${mutatorName} in file ${file.name} with replacement code "${replacement}"`); + mutants.push(mutant); + } + }); + + return mutants; + } +} \ No newline at end of file diff --git a/packages/stryker-javascript-mutator/src/NodeMutatorFactory.ts b/packages/stryker-javascript-mutator/src/NodeMutatorFactory.ts new file mode 100644 index 0000000000..978bb94b3d --- /dev/null +++ b/packages/stryker-javascript-mutator/src/NodeMutatorFactory.ts @@ -0,0 +1,23 @@ +import { Factory } from 'stryker-api/core'; +import NodeMutator from './mutators/NodeMutator'; + +namespace NodeMutatorFactory { + /** + * Represents a Factory for TestFrameworks. + */ + class NodeMutatorFactory extends Factory { + constructor() { + super('nodeMutator'); + } + } + const nodeMutatorFactoryInstance = new NodeMutatorFactory(); + + /** + * Returns the current instance of the MutatorFactory. + */ + export function instance() { + return >nodeMutatorFactoryInstance; + } +} + +export default NodeMutatorFactory; \ No newline at end of file diff --git a/packages/stryker-javascript-mutator/src/helpers/BabelParser.ts b/packages/stryker-javascript-mutator/src/helpers/BabelParser.ts new file mode 100644 index 0000000000..a065f24d99 --- /dev/null +++ b/packages/stryker-javascript-mutator/src/helpers/BabelParser.ts @@ -0,0 +1,40 @@ +import * as babel from 'babel-core'; +import * as babylon from 'babylon'; +import generate from 'babel-generator'; +import { NodePath } from 'babel-traverse'; + +export default class BabelParser { + static getAst(code: string): babel.types.File { + return babylon.parse(code); + } + + static getNodes(ast: babel.types.File): babel.types.Node[] { + const nodes: babel.types.Node[] = []; + + babel.traverse(ast, { + enter(path: NodePath) { + const node = path.node; + Object.freeze(node); + nodes.push(node); + } + }); + + return nodes; + } + + static generateCode(ast: babel.types.File, node: babel.Node) { + ast.program.body = [node as any]; + return generate(ast).code; + } + + static removeUseStrict(ast: babel.types.File) { + if (ast.program.directives) { + const directives = ast.program.directives; + directives.forEach((directive, index) => { + if (directive.value.value === 'use strict') { + directives.splice(index, 1); + } + }); + } + } +} \ No newline at end of file diff --git a/packages/stryker-javascript-mutator/src/helpers/NodeGenerator.ts b/packages/stryker-javascript-mutator/src/helpers/NodeGenerator.ts new file mode 100644 index 0000000000..c8b716f5f6 --- /dev/null +++ b/packages/stryker-javascript-mutator/src/helpers/NodeGenerator.ts @@ -0,0 +1,13 @@ +import { types } from 'babel-core'; + +export default class NodeGenerator { + static createBooleanLiteralNode(originalNode: types.Node, value: boolean): types.BooleanLiteral { + return { + start: originalNode.start, + end: originalNode.end, + loc: originalNode.loc, + type: 'BooleanLiteral', + value: value + }; + } +} \ No newline at end of file diff --git a/packages/stryker-javascript-mutator/src/helpers/copy.ts b/packages/stryker-javascript-mutator/src/helpers/copy.ts new file mode 100644 index 0000000000..980ed773e9 --- /dev/null +++ b/packages/stryker-javascript-mutator/src/helpers/copy.ts @@ -0,0 +1,9 @@ +import * as _ from 'lodash'; + +export default (obj: T, deep?: boolean) => { + if (deep) { + return _.cloneDeep(obj); + } else { + return _.clone(obj); + } +}; \ No newline at end of file diff --git a/packages/stryker-javascript-mutator/src/index.ts b/packages/stryker-javascript-mutator/src/index.ts new file mode 100644 index 0000000000..7de3790eaa --- /dev/null +++ b/packages/stryker-javascript-mutator/src/index.ts @@ -0,0 +1,5 @@ +import { MutatorFactory } from 'stryker-api/mutant'; +import JavaScriptMutator from './JavaScriptMutator'; +require('./mutators'); + +MutatorFactory.instance().register('javascript', JavaScriptMutator); \ No newline at end of file diff --git a/packages/stryker-javascript-mutator/src/mutators/ArrayLiteralMutator.ts b/packages/stryker-javascript-mutator/src/mutators/ArrayLiteralMutator.ts new file mode 100644 index 0000000000..a526ca4c94 --- /dev/null +++ b/packages/stryker-javascript-mutator/src/mutators/ArrayLiteralMutator.ts @@ -0,0 +1,21 @@ +import { types } from 'babel-core'; +import NodeMutator from './NodeMutator'; + +/** + * Represents a mutator which can remove the content of an array's elements. + */ +export default class ArrayLiteralMutator implements NodeMutator { + name = 'ArrayLiteral'; + + mutate(node: types.Node, copy: (obj: T, deep?: boolean) => T): void | types.Node[] { + let nodes: types.Node[] = []; + + if (types.isArrayExpression(node) && node.elements.length > 0) { + let mutatedNode = copy(node); + mutatedNode.elements = []; + nodes.push(mutatedNode); + } + + return nodes; + } +} \ No newline at end of file diff --git a/packages/stryker-javascript-mutator/src/mutators/ArrayNewExpressionMutator.ts b/packages/stryker-javascript-mutator/src/mutators/ArrayNewExpressionMutator.ts new file mode 100644 index 0000000000..500efa5db9 --- /dev/null +++ b/packages/stryker-javascript-mutator/src/mutators/ArrayNewExpressionMutator.ts @@ -0,0 +1,21 @@ +import { types } from 'babel-core'; +import NodeMutator from './NodeMutator'; + +/** + * Represents a mutator which can remove the content of an array's elements. + */ +export default class ArrayNewExpressionMutator implements NodeMutator { + name = 'ArrayNewExpression'; + + mutate(node: types.Node, copy: (obj: T, deep?: boolean) => T): void | types.Node[] { + let nodes: types.Node[] = []; + + if ((types.isCallExpression(node) || types.isNewExpression(node)) && types.isIdentifier(node.callee) && node.callee.name === 'Array' && node.arguments.length > 0) { + let mutatedNode = copy(node); + mutatedNode.arguments = []; + nodes.push(mutatedNode); + } + + return nodes; + } +} \ No newline at end of file diff --git a/packages/stryker-javascript-mutator/src/mutators/BinaryExpressionMutator.ts b/packages/stryker-javascript-mutator/src/mutators/BinaryExpressionMutator.ts new file mode 100644 index 0000000000..96ddee44c7 --- /dev/null +++ b/packages/stryker-javascript-mutator/src/mutators/BinaryExpressionMutator.ts @@ -0,0 +1,41 @@ +import { types } from 'babel-core'; +import NodeMutator from './NodeMutator'; + +export default class BinaryExpressionMutator implements NodeMutator { + private operators: { [targetedOperator: string]: string | string[] } = { + '+': '-', + '-': '+', + '*': '/', + '/': '*', + '%': '*', + '<': ['<=', '>='], + '<=': ['<', '>'], + '>': ['>=', '<='], + '>=': ['>', '<'], + '==': '!=', + '!=': '==', + '===': '!==', + '!==': '===', + '||': '&&', + '&&': '||' + }; + + name = 'BinaryExpression'; + + mutate(node: types.Node, clone: (node: T, deep?: boolean) => T): void | types.Node[] { + if (types.isBinaryExpression(node) || types.isLogicalExpression(node)) { + let mutatedOperators = this.operators[node.operator]; + if (mutatedOperators) { + if (typeof mutatedOperators === 'string') { + mutatedOperators = [mutatedOperators]; + } + + return mutatedOperators.map(mutatedOperator => { + let mutatedNode = clone(node); + mutatedNode.operator = mutatedOperator as any; + return mutatedNode; + }); + } + } + } +} \ No newline at end of file diff --git a/packages/stryker-javascript-mutator/src/mutators/BlockMutator.ts b/packages/stryker-javascript-mutator/src/mutators/BlockMutator.ts new file mode 100644 index 0000000000..fc850573d5 --- /dev/null +++ b/packages/stryker-javascript-mutator/src/mutators/BlockMutator.ts @@ -0,0 +1,17 @@ +import { types } from 'babel-core'; +import NodeMutator from './NodeMutator'; + +/** + * Represents a mutator which can remove the content of a Block. + */ +export default class BlockMutator implements NodeMutator { + name = 'Block'; + + mutate(node: types.Node, copy: (obj: T, deep?: boolean) => T): void | types.Node[] { + if (types.isBlockStatement(node) && node.body.length > 0) { + let mutatedNode = copy(node); + mutatedNode.body = []; + return [mutatedNode]; + } + } +} \ No newline at end of file diff --git a/packages/stryker-javascript-mutator/src/mutators/BooleanSubstitutionMutator.ts b/packages/stryker-javascript-mutator/src/mutators/BooleanSubstitutionMutator.ts new file mode 100644 index 0000000000..950bbb8a47 --- /dev/null +++ b/packages/stryker-javascript-mutator/src/mutators/BooleanSubstitutionMutator.ts @@ -0,0 +1,19 @@ +import { types } from 'babel-core'; +import NodeMutator from './NodeMutator'; + +export default class BooleanSubstitutionMutator implements NodeMutator { + name = 'BooleanSubstitution'; + + mutate(node: types.Node, copy: (obj: T, deep?: boolean) => T): types.Node[] { + const nodes: types.Node[] = []; + + // true -> false or false -> true + if (types.isBooleanLiteral(node)) { + let mutatedNode = copy(node); + mutatedNode.value = !mutatedNode.value; + nodes.push(mutatedNode); + } + + return nodes; + } +} \ No newline at end of file diff --git a/packages/stryker-javascript-mutator/src/mutators/ConditionalExpressionMutator.ts b/packages/stryker-javascript-mutator/src/mutators/ConditionalExpressionMutator.ts new file mode 100644 index 0000000000..a5db4c7f41 --- /dev/null +++ b/packages/stryker-javascript-mutator/src/mutators/ConditionalExpressionMutator.ts @@ -0,0 +1,21 @@ +import { types } from 'babel-core'; +import NodeMutator from './NodeMutator'; +import NodeGenerator from '../helpers/NodeGenerator'; + +/** + * Represents a mutator which can remove the conditional clause from statements. + */ +export default class ConditionalExpressionMutator implements NodeMutator { + name = 'ConditionalExpression'; + + constructor() { } + + mutate(node: types.Node, copy: (obj: T, deep?: boolean) => T): types.Node[] | void { + if (types.isConditionalExpression(node)) { + return [ + NodeGenerator.createBooleanLiteralNode(node.test, false), + NodeGenerator.createBooleanLiteralNode(node.test, true) + ]; + } + } +} \ No newline at end of file diff --git a/packages/stryker-javascript-mutator/src/mutators/DoStatementMutator.ts b/packages/stryker-javascript-mutator/src/mutators/DoStatementMutator.ts new file mode 100644 index 0000000000..9ba062489f --- /dev/null +++ b/packages/stryker-javascript-mutator/src/mutators/DoStatementMutator.ts @@ -0,0 +1,18 @@ +import { types } from 'babel-core'; +import NodeMutator from './NodeMutator'; +import NodeGenerator from '../helpers/NodeGenerator'; + +/** + * Represents a mutator which can remove the conditional clause from statements. + */ +export default class DoStatementMutator implements NodeMutator { + name = 'DoStatement'; + + constructor() { } + + mutate(node: types.Node, copy: (obj: T, deep?: boolean) => T): types.Node[] | void { + if (types.isDoWhileStatement(node)) { + return [NodeGenerator.createBooleanLiteralNode(node.test, false)]; + } + } +} \ No newline at end of file diff --git a/packages/stryker-javascript-mutator/src/mutators/ForStatementMutator.ts b/packages/stryker-javascript-mutator/src/mutators/ForStatementMutator.ts new file mode 100644 index 0000000000..6d13b7b3d5 --- /dev/null +++ b/packages/stryker-javascript-mutator/src/mutators/ForStatementMutator.ts @@ -0,0 +1,24 @@ +import { types } from 'babel-core'; +import NodeMutator from './NodeMutator'; +import NodeGenerator from '../helpers/NodeGenerator'; + +/** + * Represents a mutator which can remove the conditional clause from statements. + */ +export default class ForStatementMutator implements NodeMutator { + name = 'ForStatement'; + + constructor() { } + + mutate(node: types.Node, copy: (obj: T, deep?: boolean) => T): types.Node[] | void { + if (types.isForStatement(node)) { + if (!node.test) { + let mutatedNode = copy(node) as types.ForStatement; + mutatedNode.test = NodeGenerator.createBooleanLiteralNode(node, false); + return [mutatedNode]; + } else { + return [NodeGenerator.createBooleanLiteralNode(node.test, false)]; + } + } + } +} \ No newline at end of file diff --git a/packages/stryker-javascript-mutator/src/mutators/IfStatementMutator.ts b/packages/stryker-javascript-mutator/src/mutators/IfStatementMutator.ts new file mode 100644 index 0000000000..1ed58d6091 --- /dev/null +++ b/packages/stryker-javascript-mutator/src/mutators/IfStatementMutator.ts @@ -0,0 +1,21 @@ +import { types } from 'babel-core'; +import NodeMutator from './NodeMutator'; +import NodeGenerator from '../helpers/NodeGenerator'; + +/** + * Represents a mutator which can remove the conditional clause from statements. + */ +export default class IfStatementMutator implements NodeMutator { + name = 'IfStatement'; + + constructor() { } + + mutate(node: types.Node, copy: (obj: T, deep?: boolean) => T): types.Node[] | void { + if (types.isIfStatement(node)) { + return [ + NodeGenerator.createBooleanLiteralNode(node.test, false), + NodeGenerator.createBooleanLiteralNode(node.test, true) + ]; + } + } +} \ No newline at end of file diff --git a/packages/stryker-javascript-mutator/src/mutators/NodeMutator.ts b/packages/stryker-javascript-mutator/src/mutators/NodeMutator.ts new file mode 100644 index 0000000000..6d53ac87c7 --- /dev/null +++ b/packages/stryker-javascript-mutator/src/mutators/NodeMutator.ts @@ -0,0 +1,23 @@ +import { types } from 'babel-core'; + +/** + * Represents a class which can mutate parts of an Abstract Syntax Tree. + */ +export default interface NodeMutator { + /** + * The name of the Mutator which may be used by reporters. + */ + name: string; + + /** + * Applies the Mutator to a Node. This can result in one or more mutated Nodes, or null if no mutation was applied. + * This method will be called on every node of the abstract syntax tree, + * implementing mutators should decide themselves if they want to mutate this specific node. + * If the mutator wants to mutate the node, it should return a clone of the node with mutations, + * otherwise null. + * @param node A FROZEN Node which could be cloned and mutated. + * @param copy A function to create a copy of an object. + * @returns An array of mutated Nodes. + */ + mutate(node: types.Node, copy: (obj: T, deep?: boolean) => T): void | types.Node[]; +} \ No newline at end of file diff --git a/packages/stryker-javascript-mutator/src/mutators/PostfixUnaryExpressionMutator.ts b/packages/stryker-javascript-mutator/src/mutators/PostfixUnaryExpressionMutator.ts new file mode 100644 index 0000000000..fd7a17eb5c --- /dev/null +++ b/packages/stryker-javascript-mutator/src/mutators/PostfixUnaryExpressionMutator.ts @@ -0,0 +1,19 @@ +import { types } from 'babel-core'; +import NodeMutator from './NodeMutator'; + +export default class PostfixUnaryExpressionMutator implements NodeMutator { + name = 'PostfixUnaryExpression'; + + private operators: { [targetedOperator: string]: string } = { + '++': '--', + '--': '++' + }; + + mutate(node: types.Node, copy: (obj: T, deep?: boolean) => T): void | types.Node[] { + if (types.isUpdateExpression(node) && !node.prefix && this.operators[node.operator]) { + let mutatedNode = copy(node); + mutatedNode.operator = this.operators[node.operator] as any; + return [mutatedNode]; + } + } +} \ No newline at end of file diff --git a/packages/stryker-javascript-mutator/src/mutators/PrefixUnaryExpressionMutator.ts b/packages/stryker-javascript-mutator/src/mutators/PrefixUnaryExpressionMutator.ts new file mode 100644 index 0000000000..8b76b6fe1f --- /dev/null +++ b/packages/stryker-javascript-mutator/src/mutators/PrefixUnaryExpressionMutator.ts @@ -0,0 +1,29 @@ +import { types } from 'babel-core'; +import NodeMutator from './NodeMutator'; + +export default class PrefixUnaryExpressionMutator implements NodeMutator { + name = 'PrefixUnaryExpression'; + + private operators: { [targetedOperator: string]: string } = { + '+': '-', + '-': '+', + '++': '--', + '--': '++', + '~': '', + '!': '' + }; + + mutate(node: types.Node, copy: (obj: T, deep?: boolean) => T): void | types.Node[] { + if ((types.isUpdateExpression(node) || types.isUnaryExpression(node)) && this.operators[node.operator] !== undefined && node.prefix) { + if (this.operators[node.operator].length > 0) { + let mutatedNode = copy(node); + mutatedNode.operator = this.operators[node.operator] as any; + return [mutatedNode]; + } else { + let mutatedNode = copy(node.argument); + mutatedNode.start = node.start; + return [mutatedNode]; + } + } + } +} \ No newline at end of file diff --git a/packages/stryker-javascript-mutator/src/mutators/WhileStatementMutator.ts b/packages/stryker-javascript-mutator/src/mutators/WhileStatementMutator.ts new file mode 100644 index 0000000000..7ed07cfa74 --- /dev/null +++ b/packages/stryker-javascript-mutator/src/mutators/WhileStatementMutator.ts @@ -0,0 +1,18 @@ +import { types } from 'babel-core'; +import NodeMutator from './NodeMutator'; +import NodeGenerator from '../helpers/NodeGenerator'; + +/** + * Represents a mutator which can remove the conditional clause from statements. + */ +export default class WhileStatementMutator implements NodeMutator { + name = 'WhileStatement'; + + constructor() { } + + mutate(node: types.Node, copy: (obj: T, deep?: boolean) => T): types.Node[] | void { + if (types.isWhileStatement(node)) { + return [NodeGenerator.createBooleanLiteralNode(node.test, false)]; + } + } +} \ No newline at end of file diff --git a/packages/stryker-javascript-mutator/src/mutators/index.ts b/packages/stryker-javascript-mutator/src/mutators/index.ts new file mode 100644 index 0000000000..5715af5855 --- /dev/null +++ b/packages/stryker-javascript-mutator/src/mutators/index.ts @@ -0,0 +1,27 @@ +import NodeMutatorFactory from '../NodeMutatorFactory'; +import ArrayLiteralMutator from './ArrayLiteralMutator'; +import ArrayNewExpressionMutator from './ArrayNewExpressionMutator'; +import BinaryExpressionMutator from './BinaryExpressionMutator'; +import BlockMutator from './BlockMutator'; +import BooleanSubstitutionMutator from './BooleanSubstitutionMutator'; +import ConditionalExpressionMutator from './ConditionalExpressionMutator'; +import DoStatementMutator from './DoStatementMutator'; +import ForStatementMutator from './ForStatementMutator'; +import IfStatementMutator from './IfStatementMutator'; +import PrefixUnaryExpressionMutator from './PrefixUnaryExpressionMutator'; +import PostfixUnaryExpressionMutator from './PostfixUnaryExpressionMutator'; +import WhileStatementMutator from './WhileStatementMutator'; + +const factory = NodeMutatorFactory.instance(); +factory.register(ArrayLiteralMutator.name, ArrayLiteralMutator); +factory.register(ArrayNewExpressionMutator.name, ArrayNewExpressionMutator); +factory.register(BinaryExpressionMutator.name, BinaryExpressionMutator); +factory.register(BlockMutator.name, BlockMutator); +factory.register(BooleanSubstitutionMutator.name, BooleanSubstitutionMutator); +factory.register(ConditionalExpressionMutator.name, ConditionalExpressionMutator); +factory.register(DoStatementMutator.name, DoStatementMutator); +factory.register(ForStatementMutator.name, ForStatementMutator); +factory.register(IfStatementMutator.name, IfStatementMutator); +factory.register(PrefixUnaryExpressionMutator.name, PrefixUnaryExpressionMutator); +factory.register(PostfixUnaryExpressionMutator.name, PostfixUnaryExpressionMutator); +factory.register(WhileStatementMutator.name, WhileStatementMutator); \ No newline at end of file diff --git a/packages/stryker-javascript-mutator/test/helpers/LogMock.ts b/packages/stryker-javascript-mutator/test/helpers/LogMock.ts new file mode 100644 index 0000000000..2b4ba4c148 --- /dev/null +++ b/packages/stryker-javascript-mutator/test/helpers/LogMock.ts @@ -0,0 +1,19 @@ +import { Logger } from 'log4js'; +import { SinonStub } from 'sinon'; + +export default class LogMock implements Mock { + setLevel: SinonStub = sandbox.stub(); + isLevelEnabled = sandbox.stub(); + isTraceEnabled = sandbox.stub(); + isDebugEnabled = sandbox.stub(); + isInfoEnabled = sandbox.stub(); + isWarnEnabled = sandbox.stub(); + isErrorEnabled = sandbox.stub(); + isFatalEnabled = sandbox.stub(); + trace = sandbox.stub(); + debug = sandbox.stub(); + info = sandbox.stub(); + warn = sandbox.stub(); + error = sandbox.stub(); + fatal = sandbox.stub(); +} \ No newline at end of file diff --git a/packages/stryker-javascript-mutator/test/helpers/globals.ts b/packages/stryker-javascript-mutator/test/helpers/globals.ts new file mode 100644 index 0000000000..2821e72e11 --- /dev/null +++ b/packages/stryker-javascript-mutator/test/helpers/globals.ts @@ -0,0 +1,29 @@ +declare const sandbox: sinon.SinonSandbox; +declare const logMock: LogMock; +namespace NodeJS { + export interface Global { + sandbox: sinon.SinonSandbox; + logMock: LogMock; + } +} + +type Mock = { + [P in keyof T]: sinon.SinonStub; +}; + +interface LogMock { + setLevel: sinon.SinonStub; + isLevelEnabled: sinon.SinonStub; + isTraceEnabled: sinon.SinonStub; + isDebugEnabled: sinon.SinonStub; + isInfoEnabled: sinon.SinonStub; + isWarnEnabled: sinon.SinonStub; + isErrorEnabled: sinon.SinonStub; + isFatalEnabled: sinon.SinonStub; + trace: sinon.SinonStub; + debug: sinon.SinonStub; + info: sinon.SinonStub; + warn: sinon.SinonStub; + error: sinon.SinonStub; + fatal: sinon.SinonStub; +} \ No newline at end of file diff --git a/packages/stryker-javascript-mutator/test/helpers/initSinon.ts b/packages/stryker-javascript-mutator/test/helpers/initSinon.ts new file mode 100644 index 0000000000..cecacc47ee --- /dev/null +++ b/packages/stryker-javascript-mutator/test/helpers/initSinon.ts @@ -0,0 +1,13 @@ +import * as sinon from 'sinon'; +import * as log4js from 'log4js'; +import LogMock from './LogMock'; + +beforeEach(() => { + global.sandbox = sinon.sandbox.create(); + global.logMock = new LogMock(); + global.sandbox.stub(log4js, 'getLogger').returns(global.logMock); +}); + +afterEach(() => { + global.sandbox.restore(); +}); \ No newline at end of file diff --git a/packages/stryker-javascript-mutator/test/helpers/initSourceMaps.ts b/packages/stryker-javascript-mutator/test/helpers/initSourceMaps.ts new file mode 100644 index 0000000000..f1231194f5 --- /dev/null +++ b/packages/stryker-javascript-mutator/test/helpers/initSourceMaps.ts @@ -0,0 +1 @@ +import 'source-map-support/register'; \ No newline at end of file diff --git a/packages/stryker-javascript-mutator/test/helpers/mutatorAssertions.ts b/packages/stryker-javascript-mutator/test/helpers/mutatorAssertions.ts new file mode 100644 index 0000000000..adf594ba61 --- /dev/null +++ b/packages/stryker-javascript-mutator/test/helpers/mutatorAssertions.ts @@ -0,0 +1,42 @@ +import { expect } from 'chai'; +import { TextFile, FileKind } from 'stryker-api/core'; +import { Mutant } from 'stryker-api/mutant'; +import { Config } from 'stryker-api/config'; +import JavaScriptMutator from '../../src/JavaScriptMutator'; +import NodeMutator from '../../src/mutators/NodeMutator'; +import ExpectMutation from 'stryker-mutator-specification/src/ExpectMutation'; + +export interface MutatorConstructor { + new(): NodeMutator; +} + +export function verifySpecification(specification: (name: string, expectMutation: ExpectMutation) => void, MutatorClass: MutatorConstructor): void { + specification(new MutatorClass().name, (actual: string, ...expected: string[]) => expectMutation(new MutatorClass(), actual, ...expected)); +} + +export function expectMutation(mutator: NodeMutator, sourceText: string, ...expectedTexts: string[]) { + const javaScriptMutator = new JavaScriptMutator(new Config(), [mutator]); + const sourceFile: TextFile = { + content: sourceText, + included: true, + mutated: true, + name: 'file.js', + transpiled: true, + kind: FileKind.Text + }; + const mutants = javaScriptMutator.mutate([sourceFile]); + expect(mutants).lengthOf(expectedTexts.length); + const actualMutantTexts = mutants.map(mutant => mutantToString(mutant, sourceText)); + expectedTexts.forEach(expected => expect(actualMutantTexts, `was: ${actualMutantTexts.join(',')}`).to.include(expected)); +} + +/** + * Place the mutant in the sourceText and remove all new-line tokens and excess whitespace in the mutant replacement + * @param mutant + * @param sourceText + */ +function mutantToString(mutant: Mutant, sourceText: string) { + return sourceText.substr(0, mutant.range[0]) + + mutant.replacement.replace(/\s{2,}/g, ' ').replace(/\n/g, ' ') + + sourceText.substr(mutant.range[1]); +} \ No newline at end of file diff --git a/packages/stryker-javascript-mutator/test/helpers/registerChaiPlugins.ts b/packages/stryker-javascript-mutator/test/helpers/registerChaiPlugins.ts new file mode 100644 index 0000000000..3f9437376e --- /dev/null +++ b/packages/stryker-javascript-mutator/test/helpers/registerChaiPlugins.ts @@ -0,0 +1,5 @@ +import * as sinonChai from 'sinon-chai'; +import * as chaiAsPromised from 'chai-as-promised'; +import * as chai from 'chai'; +chai.use(sinonChai); +chai.use(chaiAsPromised); diff --git a/packages/stryker-javascript-mutator/test/unit/JavaScriptMutatorSpec.ts b/packages/stryker-javascript-mutator/test/unit/JavaScriptMutatorSpec.ts new file mode 100644 index 0000000000..4ec44ba5ed --- /dev/null +++ b/packages/stryker-javascript-mutator/test/unit/JavaScriptMutatorSpec.ts @@ -0,0 +1,76 @@ +import { expect } from 'chai'; +import { File, FileKind } from 'stryker-api/core'; +import JavaScriptMutator from '../../src/JavaScriptMutator'; +import '../../src/index'; +import { Config } from 'stryker-api/config'; + +describe('JavaScriptMutator', () => { + + it('should generate a correct mutant', () => { + const mutator = new JavaScriptMutator(new Config()); + const files: File[] = [ + { + name: 'testFile.js', + included: false, + mutated: true, + transpiled: false, + kind: FileKind.Text, + content: '"use strict"; var a = 1 + 2;' + } + ]; + + const mutants = mutator.mutate(files); + + expect(mutants.length).to.equal(1); + expect(mutants[0]).to.deep.equal({ + mutatorName: 'BinaryExpression', + fileName: files[0].name, + range: [22, 27], + replacement: '1 - 2' + }); + }); + + + it('should generate mutants for multiple files', () => { + let mutator = new JavaScriptMutator(new Config()); + let file: File = { + name: 'testFile.js', + included: false, + mutated: true, + transpiled: false, + kind: FileKind.Text, + content: '"use strict"; var a = 1 + 2;' + }; + + let mutants = mutator.mutate([file, file]); + + expect(mutants.length).to.equal(2); + }); + + it('should not mutate files with mutate: false', () => { + let mutator = new JavaScriptMutator(new Config()); + let files: File[] = [ + { + name: 'testFile.js', + included: false, + mutated: false, + transpiled: false, + kind: FileKind.Text, + content: '"use strict"; var a = 1 + 2;' + }, + { + name: 'testFile2.js', + included: false, + mutated: true, + transpiled: false, + kind: FileKind.Text, + content: '"use strict"; var a = 1 + 2;' + } + ]; + + let mutants = mutator.mutate(files); + + expect(mutants.length).to.equal(1); + expect(mutants[0].fileName).to.equal('testFile2.js'); + }); +}); \ No newline at end of file diff --git a/packages/stryker-javascript-mutator/test/unit/helpers/BabelParserSpec.ts b/packages/stryker-javascript-mutator/test/unit/helpers/BabelParserSpec.ts new file mode 100644 index 0000000000..2d302055f1 --- /dev/null +++ b/packages/stryker-javascript-mutator/test/unit/helpers/BabelParserSpec.ts @@ -0,0 +1,60 @@ +import { expect } from 'chai'; +import BabelParser from '../../../src/helpers/BabelParser'; + +describe('BabelParser', () => { + describe('removeUseStrict', () => { + it('should be able to remove "use strict"', () => { + const ast = BabelParser.getAst('"use strict"; var a = 1 + 2;'); + + BabelParser.removeUseStrict(ast); + + expect(ast.program.directives).to.deep.equal([]); + }); + + it('should do nothing if there is no "use strict"', () => { + const ast = BabelParser.getAst('var a = 1 + 2;'); + + BabelParser.removeUseStrict(ast); + + expect(ast.program.directives).to.deep.equal([]); + }); + }); + + describe('getNodes', () => { + it('should get the correct amount of statements', () => { + const ast = BabelParser.getAst('"use strict"; var a = 1 + 2;'); + + const nodes = BabelParser.getNodes(ast); + + expect(nodes.length).to.equal(9); + }); + }); + + describe('generateCode', () => { + it('should work with "use strict"', () => { + const ast = BabelParser.getAst('"use strict"; var a = 1 + 2;'); + + const result = BabelParser.generateCode(ast, ast.program.body[0]); + + expect(result).to.equal('"use strict";\nvar a = 1 + 2;'); + }); + + it('should work without "use strict"', () => { + const ast = BabelParser.getAst('"use strict"; var a = 1 + 2;'); + BabelParser.removeUseStrict(ast); + + const result = BabelParser.generateCode(ast, ast.program.body[0]); + + expect(result).to.equal('var a = 1 + 2;'); + }); + + it('should keep comments', () => { + const ast = BabelParser.getAst('"use strict"; var a = 1 + 2 /* Comment */;'); + BabelParser.removeUseStrict(ast); + + const result = BabelParser.generateCode(ast, ast.program.body[0]); + + expect(result).to.equal('var a = 1 + 2 /* Comment */;'); + }); + }); +}); \ No newline at end of file diff --git a/packages/stryker-javascript-mutator/test/unit/mutators/ArrayLiteralMutatorSpec.ts b/packages/stryker-javascript-mutator/test/unit/mutators/ArrayLiteralMutatorSpec.ts new file mode 100644 index 0000000000..e5506bc9b2 --- /dev/null +++ b/packages/stryker-javascript-mutator/test/unit/mutators/ArrayLiteralMutatorSpec.ts @@ -0,0 +1,15 @@ +import ArrayLiteralMutator from '../../../src/mutators/ArrayLiteralMutator'; +import { expectMutation } from '../../helpers/mutatorAssertions'; + +describe('ArrayLiteralMutator', () => { + + it('should mutate filled array literals as empty arrays', () => { + expectMutation(new ArrayLiteralMutator(), '[a, 1 + 1]', '[]'); + expectMutation(new ArrayLiteralMutator(), `['val']`, '[]'); + }); + + it('should not mutate array initializers (leave that for ArrayNewExpressionMutator)', () => { + expectMutation(new ArrayLiteralMutator(), `new Array()`); + expectMutation(new ArrayLiteralMutator(), `new Array(1, 2, 3)`); + }); +}); \ No newline at end of file diff --git a/packages/stryker-javascript-mutator/test/unit/mutators/ArrayNewExpressionMutatorSpec.ts b/packages/stryker-javascript-mutator/test/unit/mutators/ArrayNewExpressionMutatorSpec.ts new file mode 100644 index 0000000000..0d9212754b --- /dev/null +++ b/packages/stryker-javascript-mutator/test/unit/mutators/ArrayNewExpressionMutatorSpec.ts @@ -0,0 +1,21 @@ +import ArrayNewExpressionMutator from '../../../src/mutators/ArrayNewExpressionMutator'; +import { expectMutation } from '../../helpers/mutatorAssertions'; + +describe('ArrayNewExpressionMutator', () => { + + it('should mutate filled array literals as empty arrays', () => { + expectMutation(new ArrayNewExpressionMutator(), 'new Array(a, 1 + 1)', 'new Array()'); + expectMutation(new ArrayNewExpressionMutator(), `new Array('val')`, 'new Array()'); + }); + + it('should not mutate array literals (leave that for ArrayLiteralMutator)', () => { + expectMutation(new ArrayNewExpressionMutator(), `[]`); + expectMutation(new ArrayNewExpressionMutator(), `[1, 2 ,3]`); + }); + + it('should not mutate other new expressions', () => { + expectMutation(new ArrayNewExpressionMutator(), 'new Object(21, 2)'); + expectMutation(new ArrayNewExpressionMutator(), 'new Arrays(21, 2)'); + }); + +}); \ No newline at end of file diff --git a/packages/stryker-javascript-mutator/test/unit/mutators/BinaryExpressionMutatorSpec.ts b/packages/stryker-javascript-mutator/test/unit/mutators/BinaryExpressionMutatorSpec.ts new file mode 100644 index 0000000000..6a1913889d --- /dev/null +++ b/packages/stryker-javascript-mutator/test/unit/mutators/BinaryExpressionMutatorSpec.ts @@ -0,0 +1,5 @@ +import BinaryExpressionMutator from '../../../src/mutators/BinaryExpressionMutator'; +import { verifySpecification } from '../../helpers/mutatorAssertions'; +import { BinaryExpressionMutatorSpec } from 'stryker-mutator-specification/src/index'; + +verifySpecification(BinaryExpressionMutatorSpec, BinaryExpressionMutator); \ No newline at end of file diff --git a/packages/stryker-javascript-mutator/test/unit/mutators/BlockMutatorSpec.ts b/packages/stryker-javascript-mutator/test/unit/mutators/BlockMutatorSpec.ts new file mode 100644 index 0000000000..1db332d464 --- /dev/null +++ b/packages/stryker-javascript-mutator/test/unit/mutators/BlockMutatorSpec.ts @@ -0,0 +1,5 @@ +import BlockMutator from '../../../src/mutators/BlockMutator'; +import { verifySpecification } from '../../helpers/mutatorAssertions'; +import { BlockMutatorSpec } from 'stryker-mutator-specification/src/index'; + +verifySpecification(BlockMutatorSpec, BlockMutator); \ No newline at end of file diff --git a/packages/stryker-javascript-mutator/test/unit/mutators/BooleanSubstitutionMutatorSpec.ts b/packages/stryker-javascript-mutator/test/unit/mutators/BooleanSubstitutionMutatorSpec.ts new file mode 100644 index 0000000000..4eb1929855 --- /dev/null +++ b/packages/stryker-javascript-mutator/test/unit/mutators/BooleanSubstitutionMutatorSpec.ts @@ -0,0 +1,5 @@ +import BooleanSubstitutionMutator from '../../../src/mutators/BooleanSubstitutionMutator'; +import { verifySpecification } from '../../helpers/mutatorAssertions'; +import { BooleanSubstitutionMutatorSpec } from 'stryker-mutator-specification/src/index'; + +verifySpecification(BooleanSubstitutionMutatorSpec, BooleanSubstitutionMutator); \ No newline at end of file diff --git a/packages/stryker-javascript-mutator/test/unit/mutators/ConditionalExpressionMutatorSpec.ts b/packages/stryker-javascript-mutator/test/unit/mutators/ConditionalExpressionMutatorSpec.ts new file mode 100644 index 0000000000..79b11d685a --- /dev/null +++ b/packages/stryker-javascript-mutator/test/unit/mutators/ConditionalExpressionMutatorSpec.ts @@ -0,0 +1,5 @@ +import ConditionalExpressionMutator from '../../../src/mutators/ConditionalExpressionMutator'; +import { verifySpecification } from '../../helpers/mutatorAssertions'; +import { ConditionalExpressionMutatorSpec } from 'stryker-mutator-specification/src/index'; + +verifySpecification(ConditionalExpressionMutatorSpec, ConditionalExpressionMutator); \ No newline at end of file diff --git a/packages/stryker-javascript-mutator/test/unit/mutators/DoMutatorSpec.ts b/packages/stryker-javascript-mutator/test/unit/mutators/DoMutatorSpec.ts new file mode 100644 index 0000000000..95c4fb78b3 --- /dev/null +++ b/packages/stryker-javascript-mutator/test/unit/mutators/DoMutatorSpec.ts @@ -0,0 +1,5 @@ +import DoStatementMutator from '../../../src/mutators/DoStatementMutator'; +import { verifySpecification } from '../../helpers/mutatorAssertions'; +import { DoStatementMutatorSpec } from 'stryker-mutator-specification/src/index'; + +verifySpecification(DoStatementMutatorSpec, DoStatementMutator); \ No newline at end of file diff --git a/packages/stryker-javascript-mutator/test/unit/mutators/ForStatementMutatorSpec.ts b/packages/stryker-javascript-mutator/test/unit/mutators/ForStatementMutatorSpec.ts new file mode 100644 index 0000000000..a485857421 --- /dev/null +++ b/packages/stryker-javascript-mutator/test/unit/mutators/ForStatementMutatorSpec.ts @@ -0,0 +1,5 @@ +import ForStatementMutator from '../../../src/mutators/ForStatementMutator'; +import { verifySpecification } from '../../helpers/mutatorAssertions'; +import { ForStatementMutatorSpec } from 'stryker-mutator-specification/src/index'; + +verifySpecification(ForStatementMutatorSpec, ForStatementMutator); \ No newline at end of file diff --git a/packages/stryker-javascript-mutator/test/unit/mutators/IfStatementMutatorSpec.ts b/packages/stryker-javascript-mutator/test/unit/mutators/IfStatementMutatorSpec.ts new file mode 100644 index 0000000000..d0fd27816e --- /dev/null +++ b/packages/stryker-javascript-mutator/test/unit/mutators/IfStatementMutatorSpec.ts @@ -0,0 +1,5 @@ +import IfStatementMutator from '../../../src/mutators/IfStatementMutator'; +import { verifySpecification } from '../../helpers/mutatorAssertions'; +import { IfStatementMutatorSpec } from 'stryker-mutator-specification/src/index'; + +verifySpecification(IfStatementMutatorSpec, IfStatementMutator); \ No newline at end of file diff --git a/packages/stryker-javascript-mutator/test/unit/mutators/PostfixUnaryMutatorSpec.ts b/packages/stryker-javascript-mutator/test/unit/mutators/PostfixUnaryMutatorSpec.ts new file mode 100644 index 0000000000..683bde1704 --- /dev/null +++ b/packages/stryker-javascript-mutator/test/unit/mutators/PostfixUnaryMutatorSpec.ts @@ -0,0 +1,20 @@ +import PostfixUnaryExpressionMutator from '../../../src/mutators/PostfixUnaryExpressionMutator'; +import { expectMutation } from '../../helpers/mutatorAssertions'; + +describe('PostfixUnaryExpressionMutator', () => { + it('should mutate a++ to a--', () => { + expectMutation(new PostfixUnaryExpressionMutator(), 'a++', 'a--'); + }); + + it('should mutate a-- to a++', () => { + expectMutation(new PostfixUnaryExpressionMutator(), 'a--', 'a++'); + }); + + it('should not mutate ++a to --a', () => { + expectMutation(new PostfixUnaryExpressionMutator(), '++a'); + }); + + it('should not mutate --a to ++a', () => { + expectMutation(new PostfixUnaryExpressionMutator(), '--a'); + }); +}); \ No newline at end of file diff --git a/packages/stryker-javascript-mutator/test/unit/mutators/PrefixUnaryMutatorSpec.ts b/packages/stryker-javascript-mutator/test/unit/mutators/PrefixUnaryMutatorSpec.ts new file mode 100644 index 0000000000..89c569c0b1 --- /dev/null +++ b/packages/stryker-javascript-mutator/test/unit/mutators/PrefixUnaryMutatorSpec.ts @@ -0,0 +1,5 @@ +import PrefixUnaryExpressionMutator from '../../../src/mutators/PrefixUnaryExpressionMutator'; +import { verifySpecification } from '../../helpers/mutatorAssertions'; +import { PrefixUnaryExpressionMutatorSpec } from 'stryker-mutator-specification/src/index'; + +verifySpecification(PrefixUnaryExpressionMutatorSpec, PrefixUnaryExpressionMutator); \ No newline at end of file diff --git a/packages/stryker-javascript-mutator/test/unit/mutators/WhileStatementMutatorSpec.ts b/packages/stryker-javascript-mutator/test/unit/mutators/WhileStatementMutatorSpec.ts new file mode 100644 index 0000000000..1f0c426298 --- /dev/null +++ b/packages/stryker-javascript-mutator/test/unit/mutators/WhileStatementMutatorSpec.ts @@ -0,0 +1,5 @@ +import WhileStatementMutator from '../../../src/mutators/WhileStatementMutator'; +import { verifySpecification } from '../../helpers/mutatorAssertions'; +import { WhileStatementMutatorSpec } from 'stryker-mutator-specification/src/index'; + +verifySpecification(WhileStatementMutatorSpec, WhileStatementMutator); \ No newline at end of file diff --git a/packages/stryker-javascript-mutator/tsconfig.json b/packages/stryker-javascript-mutator/tsconfig.json new file mode 100644 index 0000000000..7321cad4c9 --- /dev/null +++ b/packages/stryker-javascript-mutator/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "exclude": [ + "node_modules", + "src/**/*.d.ts", + "test/**/*.d.ts", + "testResources" + ] +} \ No newline at end of file diff --git a/packages/stryker-javascript-mutator/tslint.json b/packages/stryker-javascript-mutator/tslint.json new file mode 100644 index 0000000000..c628ee2f1b --- /dev/null +++ b/packages/stryker-javascript-mutator/tslint.json @@ -0,0 +1,58 @@ +{ + "rules": { + "class-name": true, + "comment-format": [ + true, + "check-space" + ], + "indent": [ + true, + "spaces" + ], + "no-duplicate-variable": true, + "no-eval": true, + "no-internal-module": true, + "no-trailing-whitespace": false, + "no-unsafe-finally": true, + "no-var-keyword": true, + "one-line": [ + true, + "check-open-brace", + "check-whitespace" + ], + "quotemark": [ + true, + "single" + ], + "semicolon": [ + true, + "always" + ], + "triple-equals": [ + true, + "allow-null-check" + ], + "typedef-whitespace": [ + true, + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + } + ], + "variable-name": [ + true, + "ban-keywords" + ], + "whitespace": [ + true, + "check-branch", + "check-decl", + "check-operator", + "check-separator", + "check-type" + ] + } +} \ No newline at end of file diff --git a/packages/stryker-mutator-specification/src/BlockMutatorSpec.ts b/packages/stryker-mutator-specification/src/BlockMutatorSpec.ts index e8f56956dd..c8754cb014 100644 --- a/packages/stryker-mutator-specification/src/BlockMutatorSpec.ts +++ b/packages/stryker-mutator-specification/src/BlockMutatorSpec.ts @@ -4,13 +4,12 @@ import ExpectMutation from './ExpectMutation'; export default function BlockMutatorSpec(name: string, expectMutation: ExpectMutation) { describe('BlockMutator', () => { - it('should have name "Block"', () => { expect(name).eq('Block'); }); it('should mutate the block of a function into an empty block', () => { - expectMutation('function() { return 4; }', 'function() {}'); + expectMutation('(function() { return 4; })', '(function() {})'); }); it('should mutate a single block', () => { @@ -18,7 +17,7 @@ export default function BlockMutatorSpec(name: string, expectMutation: ExpectMut }); it('should not mutate an already empty block', () => { - expectMutation('function() { }'); + expectMutation('(function() { })'); }); it('should mutate the body of an anonymous function if defined as a block', () => { diff --git a/packages/stryker-mutator-specification/src/ConditionalExpressionMutatorSpec.ts b/packages/stryker-mutator-specification/src/ConditionalExpressionMutatorSpec.ts index 93f3774684..b35ee8f7a8 100644 --- a/packages/stryker-mutator-specification/src/ConditionalExpressionMutatorSpec.ts +++ b/packages/stryker-mutator-specification/src/ConditionalExpressionMutatorSpec.ts @@ -10,7 +10,7 @@ export default function ConditionalExpressionMutatorSpec(name: string, expectMut }); it('should replace conditional expressions', () => { - expectMutation('a < 3? b : c', 'false? b : c'); + expectMutation('a < 3? b : c', 'false? b : c', 'true? b : c'); }); }); } \ No newline at end of file diff --git a/packages/stryker-mutator-specification/src/PrefixUnaryExpressionMutatorSpec.ts b/packages/stryker-mutator-specification/src/PrefixUnaryExpressionMutatorSpec.ts index 8b42a450fc..29c20cfea4 100644 --- a/packages/stryker-mutator-specification/src/PrefixUnaryExpressionMutatorSpec.ts +++ b/packages/stryker-mutator-specification/src/PrefixUnaryExpressionMutatorSpec.ts @@ -40,5 +40,13 @@ export default function PrefixUnaryExpressionMutatorSpec(name: string, expectMut it('should not mutate a-a', () => { expectMutation('a-a'); }); + + it('should not mutate a++ to a--', () => { + expectMutation('a++'); + }); + + it('should not mutate a-- to a++', () => { + expectMutation('a--'); + }); }); } \ No newline at end of file diff --git a/packages/stryker-mutator-specification/src/UnaryNotMutatorSpec.ts b/packages/stryker-mutator-specification/src/UnaryNotMutatorSpec.ts deleted file mode 100644 index 24337b859b..0000000000 --- a/packages/stryker-mutator-specification/src/UnaryNotMutatorSpec.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { expect } from 'chai'; -import ExpectMutation from './ExpectMutation'; - -export default function UnaryNotMutatorSpec(name: string, expectMutation: ExpectMutation) { - - describe('UnaryNotMutator', () => { - - it('should have name "UnaryNot"', () => { - expect(name).eq('UnaryNot'); - }); - - it('should mutate `!a` into `a`', () => { - expectMutation('!a', 'a'); - }); - - }); -} \ No newline at end of file diff --git a/packages/stryker-mutator-specification/src/index.ts b/packages/stryker-mutator-specification/src/index.ts index d3cb6a8863..8900a97f88 100644 --- a/packages/stryker-mutator-specification/src/index.ts +++ b/packages/stryker-mutator-specification/src/index.ts @@ -10,6 +10,5 @@ export { default as DoStatementMutatorSpec } from './DoStatementMutatorSpec'; export { default as ForStatementMutatorSpec } from './ForStatementMutatorSpec'; export { default as IfStatementMutatorSpec } from './IfStatementMutatorSpec'; export { default as PrefixUnaryExpressionMutatorSpec } from './PrefixUnaryExpressionMutatorSpec'; -export { default as UnaryNotMutatorSpec } from './UnaryNotMutatorSpec'; export { default as WhileStatementMutatorSpec } from './WhileStatementMutatorSpec'; export { default as StringLiteralMutatorSpec } from './StringLiteralMutatorSpec'; \ No newline at end of file diff --git a/packages/stryker-typescript/src/TypescriptMutator.ts b/packages/stryker-typescript/src/TypescriptMutator.ts index 6872384574..68bea368f9 100644 --- a/packages/stryker-typescript/src/TypescriptMutator.ts +++ b/packages/stryker-typescript/src/TypescriptMutator.ts @@ -7,7 +7,6 @@ import { filterTypescriptFiles, parseFile, getTSConfig } from './helpers/tsHelpe import NodeMutator from './mutator/NodeMutator'; import BinaryExpressionMutator from './mutator/BinaryExpressionMutator'; import BooleanSubstitutionMutator from './mutator/BooleanSubstitutionMutator'; -import UnaryNotMutator from './mutator/UnaryNotMutator'; import ArrayLiteralMutator from './mutator/ArrayLiteralMutator'; import ArrayNewExpressionMutator from './mutator/ArrayNewExpressionMutator'; import BlockMutator from './mutator/BlockMutator'; @@ -25,7 +24,6 @@ export default class TypescriptMutator { constructor(private config: Config, public mutators: NodeMutator[] = [ new BinaryExpressionMutator(), new BooleanSubstitutionMutator(), - new UnaryNotMutator(), new ArrayLiteralMutator(), new ArrayNewExpressionMutator(), new BlockMutator(), diff --git a/packages/stryker-typescript/src/mutator/ConditionalExpressionMutator.ts b/packages/stryker-typescript/src/mutator/ConditionalExpressionMutator.ts index 8c81f9fc61..fcc4c2b253 100644 --- a/packages/stryker-typescript/src/mutator/ConditionalExpressionMutator.ts +++ b/packages/stryker-typescript/src/mutator/ConditionalExpressionMutator.ts @@ -9,7 +9,10 @@ export default class ConditionalExpressionMutator extends NodeMutator { - - name: string = 'UnaryNot'; - - public guard(node: ts.Node): node is ts.PrefixUnaryExpression { - return node.kind === ts.SyntaxKind.PrefixUnaryExpression; - } - - public identifyReplacements(node: ts.PrefixUnaryExpression, sourceFile: ts.SourceFile): NodeReplacement[] { - if (node.operator === ts.SyntaxKind.ExclamationToken) { - return [ { node, replacement: node.operand.getFullText(sourceFile) }]; - } else { - return []; - } - } -} \ No newline at end of file diff --git a/packages/stryker-typescript/test/unit/mutator/UnaryNotMutatorSpec.ts b/packages/stryker-typescript/test/unit/mutator/UnaryNotMutatorSpec.ts deleted file mode 100644 index 57e2673cf7..0000000000 --- a/packages/stryker-typescript/test/unit/mutator/UnaryNotMutatorSpec.ts +++ /dev/null @@ -1,5 +0,0 @@ -import UnaryNotMutator from '../../../src/mutator/UnaryNotMutator'; -import { verifySpecification } from './mutatorAssertions'; -import UnaryNotMutatorSpec from 'stryker-mutator-specification/src/UnaryNotMutatorSpec'; - -verifySpecification(UnaryNotMutatorSpec, UnaryNotMutator); diff --git a/packages/stryker/src/mutators/ES5Mutator.ts b/packages/stryker/src/mutators/ES5Mutator.ts index f78a4531da..f44f5aff82 100644 --- a/packages/stryker/src/mutators/ES5Mutator.ts +++ b/packages/stryker/src/mutators/ES5Mutator.ts @@ -15,7 +15,6 @@ import UpdateOperatorMutator from './UpdateOperatorMutator'; import ArrayDeclaratorMutator from './ArrayDeclaratorMutator'; import BooleanSubstitutionMutator from './BooleanSubstitutionMutator'; - export default class ES5Mutator implements Mutator { private readonly log: Logger; @@ -31,6 +30,7 @@ export default class ES5Mutator implements Mutator { new BooleanSubstitutionMutator() ]) { this.log = getLogger(ES5Mutator.name); + this.log.warn(`DEPRECATED: The es5 mutator is deprecated and will be removed in the future. Please upgrade to the stryker-javascript-mutator (npm install --save-dev stryker-javascript-mutator) and set "mutator: 'javascript'" in your stryker.conf.js!`); } @@ -83,4 +83,4 @@ export default class ES5Mutator implements Mutator { }); } -} \ No newline at end of file +} diff --git a/packages/stryker/stryker.conf.js b/packages/stryker/stryker.conf.js index f47a789dcc..e4a18f4ed2 100644 --- a/packages/stryker/stryker.conf.js +++ b/packages/stryker/stryker.conf.js @@ -1,6 +1,7 @@ module.exports = function (config) { var typescript = true; + var es6 = false; if (typescript) { config.set({ @@ -29,7 +30,7 @@ module.exports = function (config) { { pattern: 'node_modules/stryker-api/src/**/*.js', included: false, mutated: false } ], coverageAnalysis: 'perTest', - mutator: 'es5' + mutator: es6 ? 'javascript' : 'es5' }); } @@ -47,7 +48,8 @@ module.exports = function (config) { require.resolve('../stryker-mocha-runner/src/index'), require.resolve('../stryker-mocha-framework/src/index'), require.resolve('../stryker-html-reporter/src/index'), - require.resolve('../stryker-typescript/src/index') + require.resolve('../stryker-typescript/src/index'), + require.resolve('../stryker-javascript-mutator/src/index') ] }); };