Skip to content

Commit 2913ed8

Browse files
committed
feat: add new rule require-meta-docs-suggestion
ESLint rules can have a [meta.docs.suggestion](https://eslint.org/docs/developer-guide/working-with-rules#rule-basics) property to indicate whether or not they provide [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions). This new rule enforces that the `meta.docs.suggestion` property is correctly enabled when a rule provides suggestions, and not enabled when a rule does not provide suggestions. This is very similar to the existing [eslint-plugin/require-meta-fixable](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/require-meta-fixable.md) rule enforcing the correct presence of the `meta.fixable` property.
1 parent a3017e2 commit 2913ed8

File tree

4 files changed

+296
-0
lines changed

4 files changed

+296
-0
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ Name | ✔️ | 🛠 | Description
6464
[prefer-replace-text](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/prefer-replace-text.md) | | | require using replaceText instead of replaceTextRange.
6565
[report-message-format](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/report-message-format.md) | | | enforce a consistent format for rule report messages
6666
[require-meta-docs-description](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/require-meta-docs-description.md) | | | require rules to implement a meta.docs.description property with the correct format
67+
[require-meta-docs-suggestion](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/require-meta-docs-suggestion.md) | | | require suggestable rules to implement a `meta.docs.suggestion` property
6768
[require-meta-docs-url](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/require-meta-docs-url.md) | | 🛠 | require rules to implement a meta.docs.url property
6869
[require-meta-fixable](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/require-meta-fixable.md) | ✔️ | | require rules to implement a meta.fixable property
6970
[require-meta-schema](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/require-meta-schema.md) | | 🛠 | require rules to implement a meta.schema property
+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# require suggestable rules to implement a `meta.docs.suggestion` property (require-meta-docs-suggestion)
2+
3+
A suggestable ESLint rule should specify the `meta.docs.suggestion` property with a value of `true`. This can make it easier for both humans and tooling to tell whether a rule provides suggestions.
4+
5+
Likewise, rules that do not report suggestions should not enable the `meta.docs.suggestion` property.
6+
7+
## Rule Details
8+
9+
This rule aims to require ESLint rules to have a `meta.docs.suggestion` property if necessary.
10+
11+
The following patterns are considered warnings:
12+
13+
```js
14+
15+
/* eslint eslint-plugin/require-meta-docs-suggestion: "error" */
16+
17+
module.exports = {
18+
meta: {}, // Missing `meta.docs.suggestion`.
19+
create(context) {
20+
context.report({
21+
node,
22+
message: 'foo',
23+
suggest: [
24+
{
25+
desc: 'Insert space at the beginning',
26+
fix: fixer => fixer.insertTextBefore(node, " ")
27+
}
28+
]
29+
});
30+
}
31+
};
32+
33+
```
34+
35+
```js
36+
37+
/* eslint eslint-plugin/require-meta-docs-suggestion: "error" */
38+
39+
module.exports = {
40+
meta: { docs: { suggestion: true } }, // Has `meta.docs.suggestion` enabled but never provides suggestions.
41+
create(context) {
42+
context.report({
43+
node,
44+
message: 'foo'
45+
});
46+
}
47+
};
48+
49+
```
50+
51+
The following patterns are not warnings:
52+
53+
```js
54+
55+
/* eslint eslint-plugin/require-meta-docs-suggestion: "error" */
56+
57+
module.exports = {
58+
meta: { docs: { suggestion: true } },
59+
create(context) {
60+
context.report({
61+
node,
62+
message: 'foo',
63+
suggest: [
64+
{
65+
desc: 'Insert space at the beginning',
66+
fix: fixer => fixer.insertTextBefore(node, " ")
67+
}
68+
]
69+
});
70+
}
71+
};
72+
73+
```
74+
75+
```js
76+
77+
/* eslint eslint-plugin/require-meta-docs-suggestion: "error" */
78+
79+
module.exports = {
80+
meta: {},
81+
create(context) {
82+
context.report({
83+
node,
84+
message: 'foo'
85+
});
86+
}
87+
};
88+
89+
```
90+
91+
## Further Reading
92+
93+
* [ESLint's suggestion API](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions)
94+
* [ESLint rule basics describing the `meta.docs.suggestion` property](https://eslint.org/docs/developer-guide/working-with-rules#rule-basics)
+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
'use strict';
2+
3+
const utils = require('../utils');
4+
5+
// ------------------------------------------------------------------------------
6+
// Rule Definition
7+
// ------------------------------------------------------------------------------
8+
9+
module.exports = {
10+
meta: {
11+
docs: {
12+
description: 'require suggestable rules to implement a `meta.docs.suggestion` property',
13+
category: 'Rules',
14+
recommended: false,
15+
},
16+
type: 'problem',
17+
messages: {
18+
shouldBeSuggestable: 'Suggestable rules should specify a `meta.docs.suggestion` property with value `true`.',
19+
shouldNotBeSuggestable: 'Non-suggestable rules should not specify a `meta.docs.suggestion` property with value `true`.',
20+
},
21+
schema: [],
22+
},
23+
24+
create (context) {
25+
const sourceCode = context.getSourceCode();
26+
const ruleInfo = utils.getRuleInfo(sourceCode.ast);
27+
let contextIdentifiers;
28+
let ruleReportsSuggestions;
29+
30+
return {
31+
Program (node) {
32+
contextIdentifiers = utils.getContextIdentifiers(context, node);
33+
},
34+
CallExpression (node) {
35+
if (
36+
node.callee.type === 'MemberExpression' &&
37+
contextIdentifiers.has(node.callee.object) &&
38+
node.callee.property.type === 'Identifier' &&
39+
node.callee.property.name === 'report' &&
40+
(node.arguments.length > 4 || (
41+
node.arguments.length === 1 &&
42+
node.arguments[0].type === 'ObjectExpression' &&
43+
node.arguments[0].properties.some(prop => utils.getKeyName(prop) === 'suggest')
44+
))
45+
) {
46+
ruleReportsSuggestions = true;
47+
}
48+
},
49+
'Program:exit' () {
50+
const docsNode = ruleInfo &&
51+
ruleInfo.meta &&
52+
ruleInfo.meta.type === 'ObjectExpression' ?
53+
ruleInfo.meta.properties.find(prop => utils.getKeyName(prop) === 'docs') : undefined;
54+
55+
const suggestionProperty = docsNode && docsNode.value.type === 'ObjectExpression' ? docsNode.value.properties.find(prop => utils.getKeyName(prop) === 'suggestion') : undefined;
56+
const suggestionPropertyValue = suggestionProperty && suggestionProperty.value.type === 'Literal' ? suggestionProperty.value.value : undefined;
57+
58+
if (ruleReportsSuggestions) {
59+
if (!suggestionProperty) {
60+
// Rule reports suggestions but is missing the `meta.docs.suggestion` property altogether.
61+
context.report({ node: ruleInfo.create, messageId: 'shouldBeSuggestable' });
62+
} else if (suggestionPropertyValue !== true) {
63+
// Rule reports suggestions but does not have `meta.docs.suggestion` property enabled.
64+
context.report({ node: suggestionProperty.value, messageId: 'shouldBeSuggestable' });
65+
}
66+
} else if (!ruleReportsSuggestions && suggestionProperty && suggestionPropertyValue === true) {
67+
// Rule does not report suggestions but has the `meta.docs.suggestion` property enabled.
68+
context.report({ node: suggestionProperty.value, messageId: 'shouldNotBeSuggestable' });
69+
}
70+
},
71+
};
72+
},
73+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
'use strict';
2+
3+
// ------------------------------------------------------------------------------
4+
// Requirements
5+
// ------------------------------------------------------------------------------
6+
7+
const rule = require('../../../lib/rules/require-meta-docs-suggestion');
8+
const RuleTester = require('eslint').RuleTester;
9+
10+
// ------------------------------------------------------------------------------
11+
// Tests
12+
// ------------------------------------------------------------------------------
13+
14+
const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
15+
ruleTester.run('require-meta-docs-suggestion', rule, {
16+
valid: [
17+
'module.exports = context => {};',
18+
// No suggestions reported, no violations reported, no docs object.
19+
`
20+
module.exports = {
21+
meta: {},
22+
create(context) {}
23+
};
24+
`,
25+
// No suggestions reported, violation reported, no docs object.
26+
`
27+
module.exports = {
28+
meta: {},
29+
create(context) {
30+
context.report({node, message});
31+
}
32+
};
33+
`,
34+
// No suggestions reported, no suggestion property.
35+
`
36+
module.exports = {
37+
meta: { docs: {} },
38+
create(context) {
39+
context.report({node, message});
40+
}
41+
};
42+
`,
43+
// No suggestions reported, no suggestion property, non-object style of reporting.
44+
`
45+
module.exports = {
46+
meta: { docs: {} },
47+
create(context) {
48+
context.report(node, message);
49+
}
50+
};
51+
`,
52+
// No suggestions reported, suggestion property set to false.
53+
`
54+
module.exports = {
55+
meta: { docs: { suggestion: false } },
56+
create(context) {
57+
context.report({node, message});
58+
}
59+
};
60+
`,
61+
// Provides suggestions, has suggestion property.
62+
`
63+
module.exports = {
64+
meta: { docs: { suggestion: true } },
65+
create(context) {
66+
context.report({node, message, suggest: []});
67+
}
68+
};
69+
`,
70+
// Spread syntax.
71+
{
72+
code: `
73+
const meta = {};
74+
module.exports = {
75+
...meta,
76+
meta: {},
77+
create(context) { context.report(node, message, data, fix); }
78+
};
79+
`,
80+
parserOptions: {
81+
ecmaVersion: 9,
82+
},
83+
},
84+
],
85+
86+
invalid: [
87+
{
88+
// Reports suggestions, no suggestion property, no docs object.
89+
code: `
90+
module.exports = {
91+
meta: {},
92+
create(context) { context.report({node, message, suggest: []}); }
93+
};
94+
`,
95+
errors: [{ messageId: 'shouldBeSuggestable', type: 'FunctionExpression' }],
96+
},
97+
{
98+
// Reports suggestions, no suggestion property, with docs object.
99+
code: `
100+
module.exports = {
101+
meta: { docs: {} },
102+
create(context) { context.report({node, loc, message, data, suggest: []}); }
103+
};
104+
`,
105+
errors: [{ messageId: 'shouldBeSuggestable', type: 'FunctionExpression' }],
106+
},
107+
{
108+
// Reports suggestions, suggestion property set to false.
109+
code: `
110+
module.exports = {
111+
meta: { docs: { suggestion: false } },
112+
create(context) { context.report({node, message, suggest: []}); }
113+
};
114+
`,
115+
errors: [{ messageId: 'shouldBeSuggestable', type: 'Literal' }],
116+
},
117+
{
118+
// Does not report suggestions, suggestion property set to true.
119+
code: `
120+
module.exports = {
121+
meta: { docs: { suggestion: true } },
122+
create(context) { context.report({node, message}); }
123+
};
124+
`,
125+
errors: [{ messageId: 'shouldNotBeSuggestable', type: 'Literal' }],
126+
},
127+
],
128+
});

0 commit comments

Comments
 (0)