Skip to content

Commit

Permalink
fix(instrumenter): don't place mutants inside delete expressions (#4742)
Browse files Browse the repository at this point in the history
Don't place mutants inside a delete expression.

Before:

```js
delete stryMutAct_9fa48(\\"11\\") ? myVariable[indexer] : (stryCov_9fa48(\\"11\\"), myVariable?.[indexer]);
```

After:

```js
stryMutAct_9fa48(\\"11\\") ? delete myVariable[indexer] : (stryCov_9fa48(\\"11\\"), delete myVariable?.[indexer]);
```
  • Loading branch information
nicojs authored Feb 20, 2024
1 parent 7497e47 commit 315087f
Show file tree
Hide file tree
Showing 4 changed files with 37 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ function isCallExpression(path: NodePath): path is NodePath<babel.types.CallExpr

function isValidExpression(path: NodePath<babel.types.Expression>) {
const parent = path.parentPath;
return !isObjectPropertyKey() && !isPartOfChain() && !parent.isTaggedTemplateExpression();
return !isObjectPropertyKey() && !isPartOfChain() && !parent.isTaggedTemplateExpression() && !isPartOfDeleteExpression();

/**
* Determines if the expression is property of an object.
Expand Down Expand Up @@ -112,12 +112,22 @@ function isValidExpression(path: NodePath<babel.types.Expression>) {
(isCallExpression(parent) && parent.node.callee === path.node))
);
}

/**
* Determines if the expression is part of a delete expression.
* @returns true if the expression is part of a delete expression
* @example
* delete foo.bar;
*/
function isPartOfDeleteExpression() {
return parent.isUnaryExpression() && parent.node.operator === 'delete';
}
}

/**
* Places the mutants with a conditional expression: `global.activeMutant === 1? mutatedCode : originalCode`;
*/
export const expressionMutantPlacer: MutantPlacer<babel.types.Expression> = {
export const expressionMutantPlacer = {
name: 'expressionMutantPlacer',
canPlace(path) {
return path.isExpression() && isValidExpression(path);
Expand All @@ -135,4 +145,4 @@ export const expressionMutantPlacer: MutantPlacer<babel.types.Expression> = {
}
path.replaceWith(expression);
},
};
} satisfies MutantPlacer<babel.types.Expression>;
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,20 @@ describe('expressionMutantPlacer', () => {
expect(expressionMutantPlacer.canPlace(templateLiteral)).false;
});

it('should be false when the parent is a delete unary expression', () => {
const memberExpression = findNodePath(parseJS('delete myVariable?.[indexer];'), (p) => p.isOptionalMemberExpression());
expect(expressionMutantPlacer.canPlace(memberExpression)).false;
});

it('should be true when the parent is a non-delete unary expression', () => {
const memberExpression = findNodePath(parseJS('void myVariable[indexer];'), (p) => p.isMemberExpression());
const memberExpression2 = findNodePath(parseJS('typeof myVariable[indexer];'), (p) => p.isMemberExpression());
const memberExpression3 = findNodePath(parseJS('throw myVariable[indexer];'), (p) => p.isMemberExpression());
expect(expressionMutantPlacer.canPlace(memberExpression)).true;
expect(expressionMutantPlacer.canPlace(memberExpression2)).true;
expect(expressionMutantPlacer.canPlace(memberExpression3)).true;
});

describe('object literals', () => {
it('should be false when the expression is a key', () => {
// A stringLiteral is considered an expression, while it is not save to place a mutant there!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,8 @@ input?.id!.toString();

bar?.baz[0]

state.stats[organization?.organization_id] = action.payload.stats;
state.stats[organization?.organization_id] = action.payload.stats;

// https://github.com/stryker-mutator/stryker-js/issues/4741
delete myVariable?.[indexer];
void foo?.();
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,9 @@ const directiveRanges = stryMutAct_9fa48(\\"6\\") ? comments.map(tryParseTSDirec
const qux = quux(stryMutAct_9fa48(\\"7\\") ? corge.cov() : (stryCov_9fa48(\\"7\\"), corge?.cov()));
stryMutAct_9fa48(\\"8\\") ? input.id!.toString() : (stryCov_9fa48(\\"8\\"), input?.id!.toString());
stryMutAct_9fa48(\\"9\\") ? bar.baz[0] : (stryCov_9fa48(\\"9\\"), bar?.baz[0]);
state.stats[stryMutAct_9fa48(\\"10\\") ? organization.organization_id : (stryCov_9fa48(\\"10\\"), organization?.organization_id)] = action.payload.stats;"
state.stats[stryMutAct_9fa48(\\"10\\") ? organization.organization_id : (stryCov_9fa48(\\"10\\"), organization?.organization_id)] = action.payload.stats;
// https://github.com/stryker-mutator/stryker-js/issues/4741
stryMutAct_9fa48(\\"11\\") ? delete myVariable[indexer] : (stryCov_9fa48(\\"11\\"), delete myVariable?.[indexer]);
void (stryMutAct_9fa48(\\"12\\") ? foo() : (stryCov_9fa48(\\"12\\"), foo?.()));"
`;

0 comments on commit 315087f

Please sign in to comment.