Skip to content

Commit

Permalink
fix(instrumenter): support typescript constructors with code before `…
Browse files Browse the repository at this point in the history
…super()` (#4757)

Support constructors in TypeScript that have some code before the `super()` call and have constructor properties or initialized class properties. In such cases, the block statement mutator is not applied.

For more info, see #4744
  • Loading branch information
nicojs authored Feb 25, 2024
1 parent ae6f1d2 commit bf85d37
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 15 deletions.
25 changes: 15 additions & 10 deletions packages/instrumenter/src/mutators/block-statement-mutator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@ function isEmpty(path: NodePath<babel.types.BlockStatement>) {
* @see https://github.com/stryker-mutator/stryker-js/issues/2474
*/
function isInvalidConstructorBody(blockStatement: NodePath<babel.types.BlockStatement>): boolean {
return !!(
return Boolean(
blockStatement.parentPath.isClassMethod() &&
blockStatement.parentPath.node.kind === 'constructor' &&
(containsTSParameterProperties(blockStatement.parentPath) || containsInitializedClassProperties(blockStatement.parentPath)) &&
hasSuperExpressionOnFirstLine(blockStatement)
blockStatement.parentPath.node.kind === 'constructor' &&
(containsTSParameterProperties(blockStatement.parentPath) || containsInitializedClassProperties(blockStatement.parentPath)) &&
hasSuperExpression(blockStatement),
);
}

Expand All @@ -62,10 +62,15 @@ function containsInitializedClassProperties(constructor: NodePath<babel.types.Cl
);
}

function hasSuperExpressionOnFirstLine(constructor: NodePath<babel.types.BlockStatement>): boolean {
return (
types.isExpressionStatement(constructor.node.body[0]) &&
types.isCallExpression(constructor.node.body[0].expression) &&
types.isSuper(constructor.node.body[0].expression.callee)
);
function hasSuperExpression(constructor: NodePath<babel.types.BlockStatement>): boolean {
let hasSuper = false;
constructor.traverse({
Super(path) {
if (path.parentPath.isCallExpression()) {
path.stop();
hasSuper = true;
}
},
});
return hasSuper;
}
5 changes: 1 addition & 4 deletions packages/instrumenter/src/transformers/babel-transformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,7 @@ export const transformBabel: AstTransformer<ScriptFormat> = (
*/
function collectMutants(path: NodePath) {
return [...mutate(path)]
.map((mutable) => {
const mutant = mutantCollector.collect(originFileName, path.node, mutable, offset);
return mutant;
})
.map((mutable) => mutantCollector.collect(originFileName, path.node, mutable, offset))
.filter((mutant) => !mutant.ignoreReason);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ describe(sut.name, () => {
expectJSMutation(sut, 'class Foo { constructor() { bar(); } }', 'class Foo { constructor() {} }');
});

it('should mutate a constructor with (typescript) parameter properties', () => {
it('should mutate a constructor with (typescript) parameter properties without a `super()` call', () => {
expectJSMutation(sut, 'class Foo { constructor(private baz: string) { bar(); } }', 'class Foo { constructor(private baz: string) {} }');
});

Expand All @@ -59,16 +59,26 @@ describe(sut.name, () => {

/**
* @see https://github.com/stryker-mutator/stryker-js/issues/2314
* @see https://github.com/stryker-mutator/stryker-js/issues/4744
*/
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(); } }');
expectJSMutation(
sut,
'class Foo extends Bar { constructor(private baz: string) { const errorBody: Body = { message: `msg: ${baz}` }; super(errorBody); } }',
);
});

/**
* @see https://github.com/stryker-mutator/stryker-js/issues/2474
* @see https://github.com/stryker-mutator/stryker-js/issues/4744
*/
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(); } }');
expectJSMutation(
sut,
'class Foo extends Bar { private baz = "qux"; constructor() { const errorBody: Body = { message: `msg: ${baz}` }; super(errorBody); } }',
);
});
});
});
12 changes: 12 additions & 0 deletions packages/instrumenter/testResources/instrumenter/super-call.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,15 @@ export class InjectionError extends TypedInjectError {
super(`Could not ${describeInjectAction(path[0])} ${path.map(name).join(' -> ')}. Cause: ${cause.message}`);
}
}

// See https://github.com/stryker-mutator/stryker-js/issues/4744
export class UniqueKeyFailedError<T> extends UnprocessableEntityException {
constructor(public readonly fields: ReadonlyArray<keyof T & string>) {
const errorBody: UnprocessableEntityBody<T> = {
status: 'uniqueness_failed',
fields,
};
super(errorBody);
console.log(`${this.message} created`);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,17 @@ export class InjectionError extends TypedInjectError {
constructor(public readonly path: InjectionTarget[], public readonly cause: Error) {
super(stryMutAct_9fa48(\\"0\\") ? \`\` : (stryCov_9fa48(\\"0\\"), \`Could not \${describeInjectAction(path[0])} \${path.map(name).join(stryMutAct_9fa48(\\"1\\") ? \\"\\" : (stryCov_9fa48(\\"1\\"), ' -> '))}. Cause: \${cause.message}\`));
}
}
// See https://github.com/stryker-mutator/stryker-js/issues/4744
export class UniqueKeyFailedError<T> extends UnprocessableEntityException {
constructor(public readonly fields: ReadonlyArray<keyof T & string>) {
const errorBody: UnprocessableEntityBody<T> = stryMutAct_9fa48(\\"2\\") ? {} : (stryCov_9fa48(\\"2\\"), {
status: stryMutAct_9fa48(\\"3\\") ? \\"\\" : (stryCov_9fa48(\\"3\\"), 'uniqueness_failed'),
fields
});
super(errorBody);
console.log(stryMutAct_9fa48(\\"4\\") ? \`\` : (stryCov_9fa48(\\"4\\"), \`\${this.message} created\`));
}
}"
`;

0 comments on commit bf85d37

Please sign in to comment.