Skip to content

Commit

Permalink
Update: prefer-regex-literal detect regex literals (fixes eslint#12840)
Browse files Browse the repository at this point in the history
The rule `prefer-regex-literal` now detects when regex literals are
unnecessarily passed to the `RegExp` constructor.
  • Loading branch information
lo1tuma committed Jan 30, 2020
1 parent 03a69db commit e7a4e97
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 8 deletions.
30 changes: 30 additions & 0 deletions docs/rules/prefer-regex-literals.md
Expand Up @@ -84,6 +84,36 @@ RegExp(`${prefix}abc`);
new RegExp(String.raw`^\d\. ${sufix}`);
```

## Options

This rule has an object option:

* `disallowRedundantWrapping` set to `true` additionally checks for unnecessarily wrapped regex literals (Default `false`).

### `disallowRedundantWrapping`

By default, this rule doesn’t check when a regex literal is unnecessarily wrapped in a `RegExp` constructor call. When the option `disallowRedundantWrapping` is set to `true`, the rule will also disallow such unnecessary patterns.

Examples of `incorrect` code for `{ "disallowRedundantWrapping": true }`

```js
/*eslint prefer-regex-literal: ["error", {"disallowRedundantWrapping": true}]*/
new RegExp(/abc/);

new RegExp(/abc/, 'u');
```

Examples of `correct` code for `{ "disallowRedundantWrapping": true }`

```js
/*eslint prefer-regex-literal: ["error", {"disallowRedundantWrapping": true}]*/
/abc/;

/abc/u;

new RegExp(/abc/, flags);
```

## Further Reading

* [MDN: Regular Expressions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions)
Expand Down
69 changes: 61 additions & 8 deletions lib/rules/prefer-regex-literals.js
Expand Up @@ -25,6 +25,15 @@ function isStringLiteral(node) {
return node.type === "Literal" && typeof node.value === "string";
}

/**
* Determines whether the given node is a regex literal.
* @param {ASTNode} node Node to check.
* @returns {boolean} True if the node is a regex literal.
*/
function isRegexLiteral(node) {
return node.type === "Literal" && Object.prototype.hasOwnProperty.call(node, "regex");
}

/**
* Determines whether the given node is a template literal without expressions.
* @param {ASTNode} node Node to check.
Expand All @@ -50,14 +59,27 @@ module.exports = {
url: "https://eslint.org/docs/rules/prefer-regex-literals"
},

schema: [],
schema: [
{
type: "object",
properties: {
disallowRedundantWrapping: {
type: "boolean",
default: false
}
},
additionalProperties: false
}
],

messages: {
unexpectedRegExp: "Use a regular expression literal instead of the 'RegExp' constructor."
unexpectedRegExp: "Use a regular expression literal instead of the 'RegExp' constructor.",
unexpectedRedundantRegExp: "Regular expression literal is unnecessarily wrapped within a 'RegExp' constructor."
}
},

create(context) {
const [{ disallowRedundantWrapping = false } = {}] = context.options;

/**
* Determines whether the given identifier node is a reference to a global variable.
Expand Down Expand Up @@ -98,6 +120,40 @@ module.exports = {
isStringRawTaggedStaticTemplateLiteral(node);
}

/**
* Determines whether the relevant arguments of the given are all static string literals.
* @param {ASTNode} node Node to check.
* @returns {boolean} True if all arguments are static strings.
*/
function hasOnlyStaticStringArguments(node) {
const args = node.arguments;

if ((args.length === 1 || args.length === 2) && args.every(isStaticString)) {
return true;
}

return false;
}

/**
* Determines whether the arguments of the given node indicate that a regex literal is unnecessarily wrapped.
* @param {ASTNode} node Node to check.
* @returns {boolean} True if the node already contains a regex literal argument.
*/
function isUnnecessarilyWrappedRegexLiteral(node) {
const args = node.arguments;

if (args.length === 1 && isRegexLiteral(args[0])) {
return true;
}

if (args.length === 2 && isRegexLiteral(args[0]) && isStaticString(args[1])) {
return true;
}

return false;
}

return {
Program() {
const scope = context.getScope();
Expand All @@ -110,12 +166,9 @@ module.exports = {
};

for (const { node } of tracker.iterateGlobalReferences(traceMap)) {
const args = node.arguments;

if (
(args.length === 1 || args.length === 2) &&
args.every(isStaticString)
) {
if (disallowRedundantWrapping && isUnnecessarilyWrappedRegexLiteral(node)) {
context.report({ node, messageId: "unexpectedRedundantRegExp" });
} else if (hasOnlyStaticStringArguments(node)) {
context.report({ node, messageId: "unexpectedRegExp" });
}
}
Expand Down
16 changes: 16 additions & 0 deletions tests/lib/rules/prefer-regex-literals.js
Expand Up @@ -41,6 +41,12 @@ ruleTester.run("prefer-regex-literals", rule, {
"new RegExp(String.raw`a${''}c`);",
"new RegExp('a' + 'b')",
"RegExp(1)",
"new RegExp(/a/, 'u');",
"new RegExp(/a/);",
{
code: "new RegExp(/a/, flags);",
options: [{ disallowRedundantWrapping: true }]
},

// invalid number of arguments
"new RegExp;",
Expand Down Expand Up @@ -177,6 +183,16 @@ ruleTester.run("prefer-regex-literals", rule, {
{
code: "RegExp('a', String.raw`g`);",
errors: [{ messageId: "unexpectedRegExp", type: "CallExpression" }]
},
{
code: "new RegExp(/a/);",
options: [{ disallowRedundantWrapping: true }],
errors: [{ messageId: "unexpectedRedundantRegExp", type: "NewExpression", line: 1, column: 1 }]
},
{
code: "new RegExp(/a/, 'u');",
options: [{ disallowRedundantWrapping: true }],
errors: [{ messageId: "unexpectedRedundantRegExp", type: "NewExpression", line: 1, column: 1 }]
}
]
});

0 comments on commit e7a4e97

Please sign in to comment.