Skip to content
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
5 changes: 5 additions & 0 deletions .changeset/polite-berries-kiss.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"eslint-plugin-regexp": minor
---

feat: support explicit comparisons to null for prefer-regexp-test
59 changes: 58 additions & 1 deletion lib/rules/prefer-regexp-test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { SourceCode, Rule } from "eslint"
import type * as ES from "estree"
import { createRule } from "../utils"
import {
Expand Down Expand Up @@ -95,10 +96,19 @@ export default createRule("prefer-regexp-test", {
const regexpText = sourceCode.text.slice(
...regexpRange,
)
const convertedComparison =
node.parent.type === "BinaryExpression" &&
isComparisonToNull(node.parent)
? convertComparison(
node.parent,
sourceCode,
)(fixer)
: [] // Do nothing
return [
fixer.replaceTextRange(stringRange, regexpText),
fixer.replaceText(memberExpr.property, "test"),
fixer.replaceTextRange(regexpRange, stringText),
...convertedComparison,
]
},
})
Expand All @@ -112,7 +122,19 @@ export default createRule("prefer-regexp-test", {
node: execNode,
messageId: "disallow",
data: { target: "RegExp#exec" },
fix: (fixer) => fixer.replaceText(execNode, "test"),
*fix(fixer) {
yield fixer.replaceText(execNode, "test")

if (
node.parent.type === "BinaryExpression" &&
isComparisonToNull(node.parent)
) {
yield* convertComparison(
node.parent,
sourceCode,
)(fixer)
}
},
})
}
},
Expand Down Expand Up @@ -148,11 +170,46 @@ function isUseBoolean(node: ES.Expression): boolean {
// e.g. if (expr) {}
return parent.test === node
}
if (parent.type === "BinaryExpression") {
// e.g. expr !== null
return isComparisonToNull(parent)
}
if (parent.type === "LogicalExpression") {
if (parent.operator === "&&" || parent.operator === "||") {
// e.g. Boolean(expr1 || expr2)
return isUseBoolean(parent)
}
}

return false
}

function isComparisonToNull(binary: ES.BinaryExpression): boolean {
return (
(binary.operator === "===" || binary.operator === "!==") &&
binary.right.type === "Literal" &&
binary.right.value === null
)
}

function convertComparison(
comparison: ES.BinaryExpression,
sourceCode: SourceCode,
): (fixer: Rule.RuleFixer) => Rule.Fix[] {
return function removeComparisonFixer(fixer: Rule.RuleFixer) {
const operator = sourceCode.getTokenBefore(
comparison.right,
({ value }) => value === comparison.operator,
)!
const beforeOperator = sourceCode.getTokenBefore(operator, {
includeComments: true,
})!

return [
fixer.removeRange([beforeOperator.range![1], comparison.range![1]]),
...(comparison.operator === "==="
? [fixer.insertTextBefore(comparison.left, "!")]
: []),
]
}
}
1 change: 1 addition & 0 deletions lib/utils/ast-utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ export function findFunction(
export type KnownMethodCall = CallExpression & {
callee: MemberExpression & { object: Expression; property: Identifier }
arguments: Expression[]
parent: Node
}
/**
* Checks whether given node is expected method call
Expand Down
64 changes: 64 additions & 0 deletions tests/lib/rules/__snapshots__/prefer-regexp-test.ts.eslintsnap
Original file line number Diff line number Diff line change
Expand Up @@ -202,3 +202,67 @@ Output:
[1] Use the `RegExp#test()` method instead of `RegExp#exec`, if you need a boolean.
[2] Use the `RegExp#test()` method instead of `String#match`, if you need a boolean.
---


Test: prefer-regexp-test >> invalid
Code:
1 |
2 | const text = 'something';
3 | const pattern = /thing/;
4 | const a = pattern.exec(test) === null;
| ^~~~ [1]
5 | const b = pattern.exec(test) !== null;
| ^~~~ [2]
6 | const c = text.match(pattern) === null;
| ^~~~~~~~~~~~~~~~~~~ [3]
7 | const d = text.match(pattern) !== null;
| ^~~~~~~~~~~~~~~~~~~ [4]
8 |

Output:
1 |
2 | const text = 'something';
3 | const pattern = /thing/;
4 | const a = !pattern.test(test);
5 | const b = pattern.test(test);
6 | const c = !pattern.test(text);
7 | const d = pattern.test(text);
8 |

[1] Use the `RegExp#test()` method instead of `RegExp#exec`, if you need a boolean.
[2] Use the `RegExp#test()` method instead of `RegExp#exec`, if you need a boolean.
[3] Use the `RegExp#test()` method instead of `String#match`, if you need a boolean.
[4] Use the `RegExp#test()` method instead of `String#match`, if you need a boolean.
---


Test: prefer-regexp-test >> invalid
Code:
1 |
2 | const text = 'something';
3 | const pattern = /thing/;
4 | const a = pattern.exec(test)=== null;
| ^~~~ [1]
5 | const b = pattern.exec(test) /* Comment */ !== null;
| ^~~~ [2]
6 | const c = text.match(pattern) === /** Comment */ null;
| ^~~~~~~~~~~~~~~~~~~ [3]
7 | const d = (text.match(pattern)) !== null;
| ^~~~~~~~~~~~~~~~~~~ [4]
8 |

Output:
1 |
2 | const text = 'something';
3 | const pattern = /thing/;
4 | const a = !pattern.test(test);
5 | const b = pattern.test(test) /* Comment */;
6 | const c = !pattern.test(text);
7 | const d = (pattern.test(text));
8 |

[1] Use the `RegExp#test()` method instead of `RegExp#exec`, if you need a boolean.
[2] Use the `RegExp#test()` method instead of `RegExp#exec`, if you need a boolean.
[3] Use the `RegExp#test()` method instead of `String#match`, if you need a boolean.
[4] Use the `RegExp#test()` method instead of `String#match`, if you need a boolean.
---
24 changes: 24 additions & 0 deletions tests/lib/rules/prefer-regexp-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ tester.run("prefer-regexp-test", rule as any, {
const pattern = /thin[[g]]/v;
if (pattern.test(text)) {}
`,
`
const text = 'something';
const pattern = /thing/;
const a = pattern.exec(test) instanceof null;
const b = pattern.exec(test) === maybeNull;
const c = pattern.exec(test).groups !== undefined;
const d = text.match(pattern) != null;
`,
],
invalid: [
`
Expand Down Expand Up @@ -98,5 +106,21 @@ tester.run("prefer-regexp-test", rule as any, {
if (pattern.exec(text)) {}
if (text.match(pattern)) {}
`,
`
const text = 'something';
const pattern = /thing/;
const a = pattern.exec(test) === null;
const b = pattern.exec(test) !== null;
const c = text.match(pattern) === null;
const d = text.match(pattern) !== null;
`,
`
const text = 'something';
const pattern = /thing/;
const a = pattern.exec(test)=== null;
const b = pattern.exec(test) /* Comment */ !== null;
const c = text.match(pattern) === /** Comment */ null;
const d = (text.match(pattern)) !== null;
`,
],
})
Loading