Skip to content

Commit cbbc49f

Browse files
New: test-case-shorthand-strings rule
1 parent eff529e commit cbbc49f

File tree

7 files changed

+706
-1
lines changed

7 files changed

+706
-1
lines changed

Diff for: .eslintrc.js

+1
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,6 @@ module.exports = {
2424
rules: {
2525
'require-jsdoc': 'error',
2626
'eslint-plugin/report-message-format': ['error', '^[^a-z].*\\.$'],
27+
'eslint-plugin/test-case-shorthand-strings': 'off',
2728
},
2829
};

Diff for: README.md

+1
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,4 @@ Name | ✔️ | 🛠 | Description
6262
[report-message-format](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/report-message-format.md) | ✔️ | | Enforces a consistent format for report messages
6363
[require-meta-fixable](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/require-meta-fixable.md) | ✔️ | | Requires a `meta.fixable` property for fixable rules
6464
[no-missing-placeholders](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/no-missing-placeholders.md) | ✔️ | | Disallows missing placeholders in rule report messages
65+
[test-case-shorthand-strings](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/test-case-shorthand-strings.md) | | | Enforces consistent usage of shorthand strings for test cases with no options

Diff for: docs/rules/test-case-shorthand-strings.md

+257
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
# Enforce consistent usage of shorthand strings for test cases with no options (test-case-shorthand-strings)
2+
3+
When writing valid test cases for rules with `RuleTester`, one can optionally include a string as a test case instead of an object, if the the test case does not use any options.
4+
5+
```js
6+
ruleTester.run('example-rule', rule, {
7+
valid: [
8+
9+
// shorthand string
10+
'validTestCase;',
11+
12+
// longform object
13+
{
14+
code: 'anotherValidTestCase;'
15+
}
16+
],
17+
invalid: [
18+
// ...
19+
]
20+
});
21+
```
22+
23+
Some developers prefer using this shorthand because it's more concise, but others prefer not to use the shorthand for consistency, so that all test cases are provided as objects. Regardless of your preference, it's generally better to be consistent throughout a project.
24+
25+
## Rule Details
26+
27+
This rule aims to enforce or disallow the use of strings as test cases.
28+
29+
### Options
30+
31+
This rule has a string option:
32+
33+
* `as-needed` (default): Requires the use of shorthand strings wherever possible.
34+
* `never`: Disallows the use of shorthand strings.
35+
* `consistent`: Requires that either all valid test cases use shorthand strings, or that no valid test cases use them.
36+
* `consistent-as-needed`: Requires that all valid test cases use the longer object form if there are any valid test cases that require the object form. Otherwise, requires all valid test cases to use shorthand strings.
37+
38+
#### `as-needed`
39+
40+
Examples of **incorrect** code for this rule with the default `as-needed` option:
41+
42+
```js
43+
/* eslint eslint-plugin/test-case-shorthand-strings: "error" */
44+
45+
ruleTester.run('example-rule', rule, {
46+
valid: [
47+
{
48+
code: 'validTestCase;'
49+
},
50+
{
51+
code: 'anotherValidTestCase;'
52+
}
53+
],
54+
invalid: []
55+
});
56+
```
57+
58+
Examples of **correct** code for this rule with the default `as-needed` option:
59+
60+
```js
61+
/* eslint eslint-plugin/test-case-shorthand-strings: "error" */
62+
63+
ruleTester.run('example-rule', rule, {
64+
valid: [
65+
'validTestCase;',
66+
'anotherValidTestCase;',
67+
{
68+
code: 'testCaseWithOption;',
69+
options: ["foo"]
70+
}
71+
],
72+
invalid: []
73+
});
74+
```
75+
76+
#### `never`
77+
78+
Examples of **incorrect** code for this rule with the `never` option:
79+
80+
```js
81+
/* eslint eslint-plugin/test-case-shorthand-strings: ["error", "never"] */
82+
83+
ruleTester.run('example-rule', rule, {
84+
valid: [
85+
'validTestCase;',
86+
'anotherValidTestCase;'
87+
],
88+
invalid: []
89+
});
90+
```
91+
92+
Examples of **correct** code for this rule with the `never` option:
93+
94+
```js
95+
/* eslint eslint-plugin/test-case-shorthand-strings: ["error", "never"] */
96+
97+
ruleTester.run('example-rule', rule, {
98+
valid: [
99+
{
100+
code: 'validTestCase;'
101+
},
102+
{
103+
code: 'anotherValidTestCase;'
104+
}
105+
],
106+
invalid: []
107+
});
108+
```
109+
110+
#### `consistent`
111+
112+
Examples of **incorrect** code for this rule with the `consistent` option:
113+
114+
```js
115+
/* eslint eslint-plugin/test-case-shorthand-strings: ["error", "consistent"] */
116+
117+
ruleTester.run('example-rule', rule, {
118+
valid: [
119+
'validTestCase;',
120+
'anotherValidTestCase;',
121+
{
122+
code: 'testCaseWithOption',
123+
options: ["foo"]
124+
}
125+
],
126+
invalid: []
127+
});
128+
```
129+
130+
Examples of **correct** code for this rule with the `consistent` option:
131+
132+
```js
133+
/* eslint eslint-plugin/test-case-shorthand-strings: ["error", "consistent"] */
134+
135+
ruleTester.run('example-rule', rule, {
136+
valid: [
137+
{
138+
code: 'validTestCase;'
139+
},
140+
{
141+
code: 'anotherValidTestCase'
142+
}
143+
{
144+
code: 'testCaseWithOption',
145+
options: ["foo"]
146+
}
147+
],
148+
invalid: []
149+
});
150+
151+
ruleTester.run('example-rule', rule, {
152+
valid: [
153+
'validTestCase;'
154+
'anotherValidTestCase'
155+
],
156+
invalid: []
157+
});
158+
159+
ruleTester.run('example-rule', rule, {
160+
valid: [
161+
{
162+
code: 'validTestCase;'
163+
},
164+
{
165+
code: 'anotherValidTestCase'
166+
}
167+
],
168+
invalid: []
169+
});
170+
```
171+
172+
#### `never`
173+
174+
Examples of **incorrect** code for this rule with the `consistent-as-needed` option:
175+
176+
```js
177+
/* eslint eslint-plugin/test-case-shorthand-strings: ["error", "consistent-as-needed"] */
178+
179+
ruleTester.run('example-rule', rule, {
180+
valid: [
181+
'validTestCase;',
182+
{
183+
code: 'anotherValidTestCase'
184+
}
185+
],
186+
invalid: []
187+
});
188+
189+
ruleTester.run('example-rule', rule, {
190+
valid: [
191+
'validTestCase;',
192+
'anotherValidTestCase;',
193+
{
194+
code: 'testCaseWithOption;',
195+
options: ['foo']
196+
}
197+
],
198+
invalid: []
199+
});
200+
201+
ruleTester.run('example-rule', rule, {
202+
valid: [
203+
{
204+
code: 'validTestCase;'
205+
},
206+
{
207+
code: 'anotherValidTestCase;'
208+
}
209+
],
210+
invalid: []
211+
});
212+
```
213+
214+
Examples of **correct** code for this rule with the `consistent-as-needed` option:
215+
216+
```js
217+
/* eslint eslint-plugin/test-case-shorthand-strings: ["error", "consistent-as-needed"] */
218+
219+
ruleTester.run('example-rule', rule, {
220+
valid: [
221+
'validTestCase;'
222+
'anotherValidTestCase;'
223+
],
224+
invalid: []
225+
});
226+
227+
ruleTester.run('example-rule', rule, {
228+
valid: [
229+
{
230+
code: 'validTestCase;'
231+
},
232+
{
233+
code: 'anotherValidTestCase;'
234+
},
235+
{
236+
code: 'testCaseWithOption;',
237+
options: ['foo']
238+
}
239+
],
240+
invalid: []
241+
});
242+
```
243+
244+
## Known Limitations
245+
246+
* Test cases which are neither object literals nor string literals are ignored by this rule.
247+
* In order to find your test cases, your test file needs to match the following common pattern:
248+
* `new RuleTester()` or `new (require('eslint')).RuleTester()` is called at the top level of the file
249+
* `ruleTester.run` is called at the top level with the same variable (or in the same expression) as the `new RuleTester` instantiation
250+
251+
## When Not To Use It
252+
253+
If you don't care about consistent usage of shorthand strings, you should not turn on this rule.
254+
255+
## Further Reading
256+
257+
* [`RuleTester` documentation](http://eslint.org/docs/developer-guide/working-with-plugins#testing)

Diff for: lib/rules/test-case-shorthand-strings.js

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/**
2+
* @fileoverview Enforce consistent usage of shorthand strings for test cases with no options
3+
* @author Teddy Katz
4+
*/
5+
6+
'use strict';
7+
8+
const utils = require('../utils');
9+
10+
// ------------------------------------------------------------------------------
11+
// Rule Definition
12+
// ------------------------------------------------------------------------------
13+
14+
module.exports = {
15+
meta: {
16+
docs: {
17+
description: 'Enforce consistent usage of shorthand strings for test cases with no options',
18+
category: 'Tests',
19+
recommended: false,
20+
},
21+
schema: [{ enum: ['as-needed', 'never', 'consistent', 'consistent-as-needed'] }],
22+
},
23+
24+
create (context) {
25+
const shorthandOption = context.options[0] || 'as-needed';
26+
// ----------------------------------------------------------------------
27+
// Helpers
28+
// ----------------------------------------------------------------------
29+
30+
/**
31+
* Reports test cases as necessary
32+
* @param {object[]} cases A list of test case nodes
33+
* @returns {void}
34+
*/
35+
function reportTestCases (cases) {
36+
const caseInfoList = cases.map(testCase => {
37+
if (testCase.type === 'Literal' || testCase.type === 'TemplateLiteral') {
38+
return { node: testCase, shorthand: true, needsLongform: false };
39+
}
40+
if (testCase.type === 'ObjectExpression') {
41+
return {
42+
node: testCase,
43+
shorthand: false,
44+
needsLongform: !(testCase.properties.length === 1 && utils.getKeyName(testCase.properties[0]) === 'code'),
45+
};
46+
}
47+
return null;
48+
}).filter(Boolean);
49+
50+
const isConsistent = new Set(caseInfoList.map(caseInfo => caseInfo.shorthand)).size <= 1;
51+
const hasCaseNeedingLongform = caseInfoList.some(caseInfo => caseInfo.needsLongform);
52+
53+
caseInfoList.filter({
54+
'as-needed': caseInfo => !caseInfo.shorthand && !caseInfo.needsLongform,
55+
never: caseInfo => caseInfo.shorthand,
56+
consistent: isConsistent ? () => false : caseInfo => caseInfo.shorthand,
57+
'consistent-as-needed': caseInfo => caseInfo.shorthand === hasCaseNeedingLongform,
58+
}[shorthandOption]).forEach(badCaseInfo => {
59+
context.report({
60+
node: badCaseInfo.node,
61+
message: 'Use {{preferred}} for this test case instead of {{actual}}.',
62+
data: {
63+
preferred: badCaseInfo.shorthand ? 'an object' : 'a string',
64+
actual: badCaseInfo.shorthand ? 'a string' : 'an object',
65+
},
66+
});
67+
});
68+
}
69+
70+
// ----------------------------------------------------------------------
71+
// Public
72+
// ----------------------------------------------------------------------
73+
74+
return {
75+
Program (ast) {
76+
utils.getTestInfo(context, ast).map(testRun => testRun.valid).filter(Boolean).forEach(reportTestCases);
77+
},
78+
};
79+
},
80+
};

0 commit comments

Comments
 (0)