From bb010e2f4f1a32822e0ec275c053d335a20bb6e5 Mon Sep 17 00:00:00 2001 From: Nico Jansen Date: Thu, 17 Sep 2020 09:35:49 +0200 Subject: [PATCH] fix(instrumenter): don't mutate constructor blocks with initialized class properties Don't mutate constructor block statements when the class contains initialized class properties. Fixes #2474 --- .github/workflows/mutation-testing.yml | 2 +- .../src/mutators/block-statement-mutator.ts | 45 +++++++++++++++---- .../mutators/block-statement-mutator.spec.ts | 7 +++ 3 files changed, 44 insertions(+), 10 deletions(-) diff --git a/.github/workflows/mutation-testing.yml b/.github/workflows/mutation-testing.yml index 993cf37ebf..8338c0dd68 100644 --- a/.github/workflows/mutation-testing.yml +++ b/.github/workflows/mutation-testing.yml @@ -31,6 +31,6 @@ jobs: npm install npm run build - name: Run Stryker - run: npx lerna run --scope "@stryker-mutator/{api,instrumenter,util,typescript-checker,jest-runner}" --concurrency 1 --stream stryker -- -- --concurrency 3 + run: npx lerna run --scope "@stryker-mutator/{api,instrumenter,util,typescript-checker,jest-runner,karma-runner}" --concurrency 1 --stream stryker -- -- --concurrency 3 env: STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} \ No newline at end of file diff --git a/packages/instrumenter/src/mutators/block-statement-mutator.ts b/packages/instrumenter/src/mutators/block-statement-mutator.ts index 83cdf2ce22..7fcc627bcb 100644 --- a/packages/instrumenter/src/mutators/block-statement-mutator.ts +++ b/packages/instrumenter/src/mutators/block-statement-mutator.ts @@ -18,7 +18,7 @@ export class BlockStatementMutator implements NodeMutator { } private isValid(path: NodePath) { - return !this.isEmpty(path) && !this.isConstructorBodyWithTSParameterPropertiesAndSuperCall(path); + return !this.isEmpty(path) && !this.isInvalidConstructorBody(path); } private isEmpty(path: NodePath) { @@ -26,23 +26,50 @@ export class BlockStatementMutator implements NodeMutator { } /** - * Checks to see if a statement is the body of a constructor with TS parameter properties and a super call as it's first expression. + * Checks to see if a statement is an invalid constructor body * @example + * // Invalid! * class Foo extends Bar { * constructor(public baz: string) { * super(42); * } * } + * @example + * // Invalid! + * class Foo extends Bar { + * public baz = 'string'; + * constructor() { + * super(42); + * } + * } * @see https://github.com/stryker-mutator/stryker/issues/2314 + * @see https://github.com/stryker-mutator/stryker/issues/2474 */ - private isConstructorBodyWithTSParameterPropertiesAndSuperCall(path: NodePath): boolean { + private isInvalidConstructorBody(blockStatement: NodePath): boolean { return !!( - path.parentPath.isClassMethod() && - path.parentPath.node.kind === 'constructor' && - path.parentPath.node.params.some((param) => types.isTSParameterProperty(param)) && - types.isExpressionStatement(path.node.body[0]) && - types.isCallExpression(path.node.body[0].expression) && - types.isSuper(path.node.body[0].expression.callee) + blockStatement.parentPath.isClassMethod() && + blockStatement.parentPath.node.kind === 'constructor' && + (this.containsTSParameterProperties(blockStatement.parentPath) || this.containsInitializedClassProperties(blockStatement.parentPath)) && + this.hasSuperExpressionOnFirstLine(blockStatement) + ); + } + + private containsTSParameterProperties(constructor: NodePath): boolean { + return constructor.node.params.some((param) => types.isTSParameterProperty(param)); + } + + private containsInitializedClassProperties(constructor: NodePath): boolean { + return ( + constructor.parentPath.isClassBody() && + constructor.parentPath.node.body.some((classMember) => types.isClassProperty(classMember) && classMember.value) + ); + } + + private hasSuperExpressionOnFirstLine(constructor: NodePath): boolean { + return ( + types.isExpressionStatement(constructor.node.body[0]) && + types.isCallExpression(constructor.node.body[0].expression) && + types.isSuper(constructor.node.body[0].expression.callee) ); } } diff --git a/packages/instrumenter/test/unit/mutators/block-statement-mutator.spec.ts b/packages/instrumenter/test/unit/mutators/block-statement-mutator.spec.ts index 9c97dc1867..874011770e 100644 --- a/packages/instrumenter/test/unit/mutators/block-statement-mutator.spec.ts +++ b/packages/instrumenter/test/unit/mutators/block-statement-mutator.spec.ts @@ -68,5 +68,12 @@ describe(BlockStatementMutator.name, () => { it('should not mutate a constructor containing a super call and has (typescript) parameter properties', () => { expectJSMutation(sut, 'class Foo extends Bar { constructor(private baz: string) { super(); } }'); }); + + /** + * @see https://github.com/stryker-mutator/stryker/issues/2474 + */ + it('should not mutate a constructor containing a super call and contains initialized properties', () => { + expectJSMutation(sut, 'class Foo extends Bar { private baz = "qux"; constructor() { super(); } }'); + }); }); });