Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(instrumenter): don't place mutants inside delete expressions #4742

Merged
merged 2 commits into from
Feb 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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?.()));"
`;