Skip to content

Commit

Permalink
feat: Add prefer-equality-matcher rule
Browse files Browse the repository at this point in the history
  • Loading branch information
mskelton committed Feb 18, 2024
1 parent a8a2ea4 commit 7029cb3
Show file tree
Hide file tree
Showing 5 changed files with 413 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ CLI option\
| [no-wait-for-selector](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-wait-for-selector.md) | Disallow usage of `page.waitForSelector()` || | 💡 |
| [no-wait-for-timeout](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-wait-for-timeout.md) | Disallow usage of `page.waitForTimeout()` || | 💡 |
| [prefer-comparison-matcher](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-comparison-matcher.md) | Suggest using the built-in comparison matchers | | 🔧 | |
| [prefer-equality-matcher](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-equality-matcher.md) | Suggest using the built-in equality matchers | | | 💡 |
| [prefer-hooks-in-order](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-hooks-in-order.md) | Prefer having hooks in a consistent order | | | |
| [prefer-hooks-on-top](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-hooks-on-top.md) | Suggest having hooks before any test cases | | | |
| [prefer-strict-equal](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-strict-equal.md) | Suggest using `toStrictEqual()` | | | 💡 |
Expand Down
29 changes: 29 additions & 0 deletions docs/rules/prefer-equality-matcher.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Suggest using the built-in equality matchers (`prefer-equality-matcher`)

Playwright has built-in matchers for expecting equality, which allow for more
readable tests and error messages if an expectation fails.

## Rule details

This rule checks for _strict_ equality checks (`===` & `!==`) in tests that
could be replaced with one of the following built-in equality matchers:

- `toBe`
- `toEqual`
- `toStrictEqual`

Examples of **incorrect** code for this rule:

```js
expect(x === 5).toBe(true);
expect(name === 'Carl').not.toEqual(true);
expect(myObj !== thatObj).toStrictEqual(true);
```

Examples of **correct** code for this rule:

```js
expect(x).toBe(5);
expect(name).not.toEqual('Carl');
expect(myObj).toStrictEqual(thatObj);
```
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import noUselessNot from './rules/no-useless-not';
import noWaitForSelector from './rules/no-wait-for-selector';
import noWaitForTimeout from './rules/no-wait-for-timeout';
import preferComparisonMatcher from './rules/prefer-comparison-matcher';
import preferEqualityMatcher from './rules/prefer-equality-matcher';
import preferHooksInOrder from './rules/prefer-hooks-in-order';
import preferHooksOnTop from './rules/prefer-hooks-on-top';
import preferLowercaseTitle from './rules/prefer-lowercase-title';
Expand Down Expand Up @@ -70,6 +71,7 @@ const index = {
'no-wait-for-selector': noWaitForSelector,
'no-wait-for-timeout': noWaitForTimeout,
'prefer-comparison-matcher': preferComparisonMatcher,
'prefer-equality-matcher': preferEqualityMatcher,
'prefer-hooks-in-order': preferHooksInOrder,
'prefer-hooks-on-top': preferHooksOnTop,
'prefer-lowercase-title': preferLowercaseTitle,
Expand Down
98 changes: 98 additions & 0 deletions src/rules/prefer-equality-matcher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { Rule } from 'eslint';
import {
equalityMatchers,
getParent,
getRawValue,
getStringValue,
isBooleanLiteral,
} from '../utils/ast';
import { parseExpectCall } from '../utils/parseExpectCall';

export default {
create(context) {
return {
CallExpression(node) {
const expectCall = parseExpectCall(context, node);
if (!expectCall || expectCall.args.length === 0) return;

const { args, matcher } = expectCall;
const [comparison] = node.arguments;
const expectCallEnd = node.range![1];
const [matcherArg] = args;

if (
comparison?.type !== 'BinaryExpression' ||
(comparison.operator !== '===' && comparison.operator !== '!==') ||
!equalityMatchers.has(getStringValue(matcher)) ||
!isBooleanLiteral(matcherArg)
) {
return;
}

const matcherValue = getRawValue(matcherArg) === 'true';
const [modifier] = expectCall.modifiers;
const hasNot = expectCall.modifiers.some(
(node) => getStringValue(node) === 'not',
);

// we need to negate the expectation if the current expected
// value is itself negated by the "not" modifier
const addNotModifier =
(comparison.operator === '!==' ? !matcherValue : matcherValue) ===
hasNot;

context.report({
messageId: 'useEqualityMatcher',
node: matcher,
suggest: [...equalityMatchers.keys()].map((equalityMatcher) => ({
data: { matcher: equalityMatcher },
fix(fixer) {
// preserve the existing modifier if it's not a negation
let modifierText =
modifier && getStringValue(modifier) !== 'not'
? `.${getStringValue(modifier)}`
: '';

if (addNotModifier) {
modifierText += `.not`;
}

return [
// replace the comparison argument with the left-hand side of the comparison
fixer.replaceText(
comparison,
context.sourceCode.getText(comparison.left),
),
// replace the current matcher & modifier with the preferred matcher
fixer.replaceTextRange(
[expectCallEnd, getParent(matcher)!.range![1]],
`${modifierText}.${equalityMatcher}`,
),
// replace the matcher argument with the right-hand side of the comparison
fixer.replaceText(
matcherArg,
context.sourceCode.getText(comparison.right),
),
];
},
messageId: 'suggestEqualityMatcher',
})),
});
},
};
},
meta: {
docs: {
category: 'Best Practices',
description: 'Suggest using the built-in equality matchers',
recommended: false,
url: 'https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-equality-matcher.md',
},
hasSuggestions: true,
messages: {
suggestEqualityMatcher: 'Use `{{ matcher }}`',
useEqualityMatcher: 'Prefer using one of the equality matchers instead',
},
type: 'suggestion',
},
} as Rule.RuleModule;
Loading

0 comments on commit 7029cb3

Please sign in to comment.