Skip to content

Commit

Permalink
fix(regex): support unicode regex flags(#3642)
Browse files Browse the repository at this point in the history
This PR adds support for unicode regexes. I.e.: `/\u{0}/gu` isn't mutated to `/\u/gu` (which is invalid).

* update dependency weapon-regex to v1 to support unicode regexes, see stryker-mutator/weapon-regex#165
* pass regex flags argument to weapon-regex.

Closes #3579

Co-authored-by: Hugo van Rijswijk <git@hugovr.nl>
  • Loading branch information
renovate[bot] and hugo-vrijswijk committed Jul 26, 2022
1 parent 842c922 commit fcf3a6b
Show file tree
Hide file tree
Showing 5 changed files with 41 additions and 31 deletions.
14 changes: 7 additions & 7 deletions packages/instrumenter/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/instrumenter/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"@stryker-mutator/api": "6.1.2",
"@stryker-mutator/util": "6.1.2",
"angular-html-parser": "~1.8.0",
"weapon-regex": "~0.6.0"
"weapon-regex": "~1.0.2"
},
"devDependencies": {
"@babel/preset-react": "7.18.6",
Expand Down
21 changes: 15 additions & 6 deletions packages/instrumenter/src/mutators/regex-mutator.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import babel, { NodePath, type types as t } from '@babel/core';
import weaponRegex from 'weapon-regex';
import * as weaponRegex from 'weapon-regex';

import { NodeMutator } from './index.js';

Expand All @@ -19,29 +19,38 @@ function isObviousRegexString(path: NodePath<t.StringLiteral>) {
path.parentPath.node.arguments[0] === path.node
);
}
const weaponRegexOptions: weaponRegex.Options = { mutationLevels: [1] };

function getFlags(path: NodePath<t.NewExpression>): string | undefined {
if (types.isStringLiteral(path.node.arguments[1])) {
return path.node.arguments[1].value;
}
return undefined;
}

const weaponRegexOptions: weaponRegex.MutationOptions = { mutationLevels: [1] };

export const regexMutator: NodeMutator = {
name: 'Regex',

*mutate(path) {
if (path.isRegExpLiteral()) {
for (const replacementPattern of mutatePattern(path.node.pattern)) {
for (const replacementPattern of mutatePattern(path.node.pattern, path.node.flags)) {
const replacement = types.regExpLiteral(replacementPattern, path.node.flags);
yield replacement;
}
} else if (path.isStringLiteral() && isObviousRegexString(path)) {
for (const replacementPattern of mutatePattern(path.node.value)) {
const flags = getFlags(path.parentPath as NodePath<t.NewExpression>);
for (const replacementPattern of mutatePattern(path.node.value, flags)) {
yield types.stringLiteral(replacementPattern);
}
}
},
};

function mutatePattern(pattern: string): string[] {
function mutatePattern(pattern: string, flags: string | undefined): string[] {
if (pattern.length) {
try {
return weaponRegex.mutate(pattern, weaponRegexOptions).map((mutant) => mutant.pattern);
return weaponRegex.mutate(pattern, flags, weaponRegexOptions).map((mutant) => mutant.pattern);
} catch (err: any) {
console.error(
`[RegexMutator]: The Regex parser of weapon-regex couldn't parse this regex pattern: "${pattern}". Please report this issue at https://github.com/stryker-mutator/weapon-regex/issues. Inner error: ${err.message}`
Expand Down
23 changes: 18 additions & 5 deletions packages/instrumenter/test/unit/mutators/regex-mutator.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { expect } from 'chai';
import sinon from 'sinon';
import weaponRegex from 'weapon-regex';

import { regexMutator as sut } from '../../../src/mutators/regex-mutator.js';
import { expectJSMutation } from '../../helpers/expect-mutation.js';
Expand All @@ -20,16 +19,14 @@ describe(sut.name, () => {

it("should not crash if a regex couldn't be parsed", () => {
// Arrange
const weaponRegexStub = sinon.stub(weaponRegex, 'mutate');
weaponRegexStub.throws(new Error('[Error] Parser: Position 1:1, found "[[]]"'));
const errorStub = sinon.stub(console, 'error');

// Act
expectJSMutation(sut, '/[[]]/');
expectJSMutation(sut, 'new RegExp("*(a|$]")');

// Assert
expect(errorStub).calledWith(
'[RegexMutator]: The Regex parser of weapon-regex couldn\'t parse this regex pattern: "[[]]". Please report this issue at https://github.com/stryker-mutator/weapon-regex/issues. Inner error: [Error] Parser: Position 1:1, found "[[]]"'
'[RegexMutator]: The Regex parser of weapon-regex couldn\'t parse this regex pattern: "*(a|$]". Please report this issue at https://github.com/stryker-mutator/weapon-regex/issues. Inner error: [Error] Parser: Position 1:1, found "*(a|$]"'
);
});

Expand All @@ -40,4 +37,20 @@ describe(sut.name, () => {
it('should not mutate the flags of a new RegExp constructor', () => {
expectJSMutation(sut, 'new RegExp("", "\\\\d{4}")');
});

it('should not pass flags if no flags are defined', () => {
expectJSMutation(sut, '/\\u{20}/', '/\\u/');
});

it('should pass flags in regex literals', () => {
expectJSMutation(sut, '/\\u{20}/u');
});

it('should pass flags in new RegExp constructors', () => {
expectJSMutation(sut, 'new RegExp("\\\\u{20}", "u")');
});

it('should only pass flags in new RegExp constructors if it is a string literal', () => {
expectJSMutation(sut, 'new RegExp("\\\\u{20}", foo)', 'new RegExp("\\\\u", foo)');
});
});
12 changes: 0 additions & 12 deletions packages/instrumenter/typings/weapon-regex.d.ts

This file was deleted.

0 comments on commit fcf3a6b

Please sign in to comment.