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(require-tothrow-message): rename rule to require-to-throw-message #306

Merged
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Expand Up @@ -141,7 +141,7 @@ installations requiring long-term consistency.
| [prefer-to-have-length][] | Suggest using `toHaveLength()` | | ![fixable-green][] |
| [prefer-todo][] | Suggest using `test.todo()` | | ![fixable-green][] |
| [require-top-level-describe][] | Require a top-level `describe` block | | |
| [require-tothrow-message][] | Require that `toThrow()` and `toThrowError` includes a message | | |
| [require-to-throw-message][] | Require that `toThrow()` and `toThrowError` includes a message | | |
| [valid-describe][] | Enforce valid `describe()` callback | ![recommended][] | |
| [valid-expect-in-promise][] | Enforce having return statement when testing with promises | ![recommended][] | |
| [valid-expect][] | Enforce valid `expect()` usage | ![recommended][] | |
Expand Down Expand Up @@ -195,7 +195,7 @@ https://github.com/dangreenisrael/eslint-plugin-jest-formatting
[prefer-to-have-length]: docs/rules/prefer-to-have-length.md
[prefer-todo]: docs/rules/prefer-todo.md
[require-top-level-describe]: docs/rules/require-top-level-describe.md
[require-tothrow-message]: docs/rules/require-tothrow-message.md
SimenB marked this conversation as resolved.
Show resolved Hide resolved
[require-to-throw-message]: docs/rules/require-to-throw-message.md
[valid-describe]: docs/rules/valid-describe.md
[valid-expect-in-promise]: docs/rules/valid-expect-in-promise.md
[valid-expect]: docs/rules/valid-expect.md
Expand Down
10 changes: 9 additions & 1 deletion src/__tests__/rules.test.ts
Expand Up @@ -2,7 +2,15 @@ import { existsSync } from 'fs';
import { resolve } from 'path';
import plugin from '../';

const ruleNames = Object.keys(plugin.rules);
const excludeRules = [
// require-tothrow-message has been renamed to require-to-throw-message, remove in major version bump
'require-tothrow-message',
];

const ruleNames = Object.keys(plugin.rules).filter(rule => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be short-handed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I generally don't use implicit returns on arrow functions because I think they are harder to work with, but it doesn't matter to me either way. Do you want me to change it?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I would, if you don't mind; we use shorthand arrow functions in most other places, and imo they can make filters easier to read by providing a clearer distinction between simple and advance filters.

For example, in this case the filter can just be filter(rule => !excludeRules.includes(rules));, which reads as "filter rules not included in excludeRules".

Any conditional more complex than this and I'd agree w/ you that it should be long form.

They tend to be nicer w/ TypeScript anyway, since you don't have to worry about returns being used by mistake if you short-hand.

return excludeRules.includes(rule) === false;
});

const numberOfRules = 39;

describe('rules', () => {
Expand Down
@@ -1,13 +1,14 @@
import { TSESLint } from '@typescript-eslint/experimental-utils';
import rule from '../require-tothrow-message';
import rule from '../require-to-throw-message';
import deprecatedRule from '../require-tothrow-message'; // remove in major version bump

const ruleTester = new TSESLint.RuleTester({
parserOptions: {
ecmaVersion: 8,
},
});

ruleTester.run('require-tothrow-message', rule, {
ruleTester.run('require-to-throw-message', rule, {
valid: [
// String
"expect(() => { throw new Error('a'); }).toThrow('a');",
Expand Down Expand Up @@ -66,8 +67,8 @@ ruleTester.run('require-tothrow-message', rule, {
code: "expect(() => { throw new Error('a'); }).toThrow();",
errors: [
{
messageId: 'requireRethrow',
data: { propertyName: 'toThrow' },
messageId: 'addErrorMessage',
data: { matcherName: 'toThrow' },
column: 41,
line: 1,
},
Expand All @@ -78,8 +79,8 @@ ruleTester.run('require-tothrow-message', rule, {
code: "expect(() => { throw new Error('a'); }).toThrowError();",
errors: [
{
messageId: 'requireRethrow',
data: { propertyName: 'toThrowError' },
messageId: 'addErrorMessage',
data: { matcherName: 'toThrowError' },
column: 41,
line: 1,
},
Expand All @@ -95,18 +96,37 @@ ruleTester.run('require-tothrow-message', rule, {
})`,
errors: [
{
messageId: 'requireRethrow',
data: { propertyName: 'toThrow' },
messageId: 'addErrorMessage',
data: { matcherName: 'toThrow' },
column: 49,
line: 3,
},
{
messageId: 'requireRethrow',
data: { propertyName: 'toThrowError' },
messageId: 'addErrorMessage',
data: { matcherName: 'toThrowError' },
column: 49,
line: 4,
},
],
},
],
});

// remove in major version bump
ruleTester.run('require-tothrow-message', deprecatedRule, {
valid: ["expect(() => { throw new Error('a'); }).toThrow('a');"],

invalid: [
{
code: "expect(() => { throw new Error('a'); }).toThrow();",
errors: [
{
messageId: 'addErrorMessage',
data: { matcherName: 'toThrow' },
column: 41,
line: 1,
},
],
},
],
});
50 changes: 50 additions & 0 deletions src/rules/require-to-throw-message.ts
@@ -0,0 +1,50 @@
import {
ModifierName,
createRule,
isExpectCall,
parseExpectCall,
} from './utils';

export default createRule({
name: __filename,
meta: {
docs: {
category: 'Best Practices',
description: 'Require a message for `toThrow()`',
recommended: false,
},
messages: {
addErrorMessage: 'Add an error message to {{ propertyName }}()',
},
type: 'suggestion',
schema: [],
},
defaultOptions: [],
create(context) {
return {
CallExpression(node) {
if (!isExpectCall(node)) {
return;
}

const { matcher, modifier } = parseExpectCall(node);

if (
matcher &&
matcher.arguments &&
matcher.arguments.length === 0 &&
['toThrow', 'toThrowError'].includes(matcher.name) &&
(!modifier ||
!(modifier.name === ModifierName.not || modifier.negation))
) {
// Look for `toThrow` calls with no arguments.
context.report({
messageId: 'addErrorMessage',
data: { matcherName: matcher.name },
node: matcher.node.property,
});
}
},
};
},
});
56 changes: 9 additions & 47 deletions src/rules/require-tothrow-message.ts
@@ -1,50 +1,12 @@
import {
ModifierName,
createRule,
isExpectCall,
parseExpectCall,
} from './utils';
import requireToThrowMessage from './require-to-throw-message';

export default createRule({
name: __filename,
meta: {
docs: {
category: 'Best Practices',
description: 'Require a message for `toThrow()`',
recommended: false,
},
messages: {
requireRethrow: 'Add an error message to {{ propertyName }}()',
},
type: 'suggestion',
schema: [],
},
defaultOptions: [],
create(context) {
return {
CallExpression(node) {
if (!isExpectCall(node)) {
return;
}
// remove this file in major version bump

const { matcher, modifier } = parseExpectCall(node);

if (
matcher &&
matcher.arguments &&
matcher.arguments.length === 0 &&
['toThrow', 'toThrowError'].includes(matcher.name) &&
(!modifier ||
!(modifier.name === ModifierName.not || modifier.negation))
) {
// Look for `toThrow` calls with no arguments.
context.report({
messageId: 'requireRethrow', // todo: rename to 'addErrorMessage'
data: { propertyName: matcher.name }, // todo: rename to 'matcherName'
node: matcher.node.property,
});
}
},
};
export default {
...requireToThrowMessage,
meta: {
...requireToThrowMessage.meta,
deprecated: true,
replacedBy: ['require-to-throw-message'],
},
});
};