Skip to content

Commit 2342d55

Browse files
committed
feat: add new rule no-only-tests
1 parent 65cfb2c commit 2342d55

File tree

4 files changed

+419
-0
lines changed

4 files changed

+419
-0
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ Name | ✔️ | 🛠 | Description
5353
[no-deprecated-report-api](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/no-deprecated-report-api.md) | ✔️ | 🛠 | disallow use of the deprecated context.report() API
5454
[no-identical-tests](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/no-identical-tests.md) | ✔️ | 🛠 | disallow identical tests
5555
[no-missing-placeholders](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/no-missing-placeholders.md) | ✔️ | | disallow missing placeholders in rule report messages
56+
[no-only-tests](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/no-only-tests.md) | | | disallow the test case property `only`
5657
[no-unused-placeholders](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/no-unused-placeholders.md) | ✔️ | | disallow unused placeholders in rule report messages
5758
[no-useless-token-range](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/no-useless-token-range.md) | ✔️ | 🛠 | disallow unnecessary calls to sourceCode.getFirstToken and sourceCode.getLastToken
5859
[prefer-object-rule](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/prefer-object-rule.md) | | 🛠 | disallow rule exports where the export is a function.

docs/rules/no-only-tests.md

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Disallow the test case property `only` (no-only-tests)
2+
3+
The [`only` property](https://eslint.org/docs/developer-guide/unit-tests#running-individual-tests) can be used as of [ESLint 7.29](https://eslint.org/blog/2021/06/eslint-v7.29.0-released#highlights) for running individual rule test cases with less-noisy debugging. This feature should be only used in development, as it prevents all the tests from running. Mistakenly checking-in a test case with this property can cause CI tests to incorrectly pass.
4+
5+
## Rule Details
6+
7+
This rule flags a violation when a test case is using `only`. Note that this rule is not autofixable since automatically deleting the property would prevent developers from being able to use it during development.
8+
9+
Examples of **incorrect** code for this rule:
10+
11+
```js
12+
/* eslint eslint-plugin/no-only-tests: error */
13+
14+
const { RuleTester } = require('eslint');
15+
const ruleTester = new RuleTester();
16+
17+
ruleTester.run('my-rule', myRule, {
18+
valid: [
19+
{
20+
code: 'const valid = 42;',
21+
only: true,
22+
},
23+
RuleTester.only('const valid = 42;'),
24+
],
25+
invalid: [
26+
{
27+
code: 'const invalid = 42;',
28+
only: true,
29+
errors: [/* ... */],
30+
},
31+
],
32+
});
33+
```
34+
35+
Examples of **correct** code for this rule:
36+
37+
```js
38+
/* eslint eslint-plugin/no-only-tests: error */
39+
40+
const { RuleTester } = require('eslint');
41+
const ruleTester = new RuleTester();
42+
43+
ruleTester.run('my-rule', myRule, {
44+
valid: [
45+
'const valid = 42;',
46+
{ code: 'const valid = 42;' },
47+
],
48+
invalid: [
49+
{
50+
code: 'const invalid = 42;',
51+
errors: [/* ... */],
52+
},
53+
],
54+
});
55+
```

lib/rules/no-only-tests.js

+112
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
'use strict';
2+
3+
const utils = require('../utils');
4+
5+
/**
6+
* Checks if the given token is a comma token or not.
7+
* From: https://github.com/eslint/eslint/blob/master/lib/rules/utils/ast-utils.js
8+
* @param {Token} token The token to check.
9+
* @returns {boolean} `true` if the token is a comma token.
10+
*/
11+
function isCommaToken (token) {
12+
return token.value === ',' && token.type === 'Punctuator';
13+
}
14+
15+
/**
16+
* Checks if the given token is an opening brace token or not.
17+
* From: https://github.com/eslint/eslint/blob/master/lib/rules/utils/ast-utils.js
18+
* @param {Token} token The token to check.
19+
* @returns {boolean} `true` if the token is an opening brace token.
20+
*/
21+
function isOpeningBraceToken (token) {
22+
return token.value === '{' && token.type === 'Punctuator';
23+
}
24+
25+
/**
26+
* Checks if the given token is a closing brace token or not.
27+
* From: https://github.com/eslint/eslint/blob/master/lib/rules/utils/ast-utils.js
28+
* @param {Token} token The token to check.
29+
* @returns {boolean} `true` if the token is a closing brace token.
30+
*/
31+
function isClosingBraceToken (token) {
32+
return token.value === '}' && token.type === 'Punctuator';
33+
}
34+
35+
module.exports = {
36+
meta: {
37+
type: 'problem',
38+
docs: {
39+
description: 'disallow the test case property `only`',
40+
category: 'Tests',
41+
recommended: false,
42+
},
43+
schema: [],
44+
messages: {
45+
foundOnly:
46+
'The test case property `only` can be used during development, but should not be checked-in, since it prevents all the tests from running.',
47+
removeOnly: 'Remove `only`.',
48+
},
49+
hasSuggestions: true,
50+
},
51+
52+
create (context) {
53+
return {
54+
Program (ast) {
55+
for (const testRun of utils.getTestInfo(context, ast)) {
56+
for (const test of [...testRun.valid, ...testRun.invalid]) {
57+
if (test.type === 'ObjectExpression') {
58+
// Test case object: { code: 'const x = 123;', ... }
59+
60+
const onlyProperty = test.properties.find(
61+
property =>
62+
property.key.type === 'Identifier' &&
63+
property.key.name === 'only' &&
64+
property.value.type === 'Literal' &&
65+
property.value.value
66+
);
67+
68+
if (onlyProperty) {
69+
context.report({
70+
node: onlyProperty,
71+
messageId: 'foundOnly',
72+
suggest: [
73+
{
74+
messageId: 'removeOnly',
75+
*fix (fixer) {
76+
const sourceCode = context.getSourceCode();
77+
78+
const tokenBefore = sourceCode.getTokenBefore(onlyProperty);
79+
const tokenAfter = sourceCode.getTokenAfter(onlyProperty);
80+
if (
81+
(isCommaToken(tokenBefore) && isCommaToken(tokenAfter)) || // In middle of properties
82+
(isOpeningBraceToken(tokenBefore) && isCommaToken(tokenAfter)) // At beginning of properties
83+
) {
84+
yield fixer.remove(tokenAfter);
85+
}
86+
if (isCommaToken(tokenBefore) && isClosingBraceToken(tokenAfter)) { // At end of properties
87+
yield fixer.remove(tokenBefore);
88+
}
89+
90+
yield fixer.remove(onlyProperty);
91+
},
92+
},
93+
],
94+
});
95+
}
96+
} else if (
97+
test.type === 'CallExpression' &&
98+
test.callee.type === 'MemberExpression' &&
99+
test.callee.object.type === 'Identifier' &&
100+
test.callee.object.name === 'RuleTester' &&
101+
test.callee.property.type === 'Identifier' &&
102+
test.callee.property.name === 'only'
103+
) {
104+
// RuleTester.only('const x = 123;');
105+
context.report({ node: test.callee, messageId: 'foundOnly' });
106+
}
107+
}
108+
}
109+
},
110+
};
111+
},
112+
};

0 commit comments

Comments
 (0)