Skip to content

Commit 21f6ad1

Browse files
committed
feat: add new rule require-meta-docs-description
The eslint core plugin has an internal lint rule to enforce this same thing: https://github.com/eslint/eslint/blob/master/tools/internal-rules/consistent-docs-description.js
1 parent beafe71 commit 21f6ad1

File tree

4 files changed

+292
-0
lines changed

4 files changed

+292
-0
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ Name | ✔️ | 🛠 | Description
6262
[prefer-placeholders](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/prefer-placeholders.md) | | | disallow template literals as report messages
6363
[prefer-replace-text](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/prefer-replace-text.md) | | | prefer using replaceText instead of replaceTextRange.
6464
[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
65+
[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
6566
[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
6667
[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
6768
[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
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# require rules to implement a meta.docs.description property (require-meta-docs-description)
2+
3+
Defining a clear and consistent description for each rule helps developers understand what they're used for.
4+
5+
In particular, each rule description should begin with an allowed prefix:
6+
* `enforce`
7+
* `require`
8+
* `disallow`
9+
10+
## Rule Details
11+
12+
This rule requires ESLint rules to have a valid `meta.docs.description` property.
13+
14+
Examples of **incorrect** code for this rule:
15+
16+
```js
17+
/* eslint eslint-plugin/require-meta-docs-description: error */
18+
module.exports = {
19+
meta: {},
20+
create: function(context) { /* ... */}
21+
};
22+
23+
module.exports = {
24+
meta: { description: 'this rule does ...' }, // missing allowed prefix
25+
create: function(context) { /* ... */}
26+
};
27+
```
28+
29+
Examples of **correct** code for this rule:
30+
31+
```js
32+
/* eslint eslint-plugin/require-meta-docs-description: error */
33+
module.exports = {
34+
meta: { description: 'disallow unused variables' },
35+
create: function(context) { /* ... */}
36+
};
37+
```
38+
39+
## Options
40+
41+
This rule takes an optional object containing:
42+
43+
- `String[]``allowedPrefixes` — A list of allowed description prefixes. Use `[]` to allow any prefix. Defaults to `['enforce', 'require', 'disallow']`.
44+
45+
## Further Reading
46+
47+
* [working-with-rules#options-schemas](https://eslint.org/docs/developer-guide/working-with-rules#options-schemas)
+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
'use strict';
2+
3+
const utils = require('../utils');
4+
5+
// ------------------------------------------------------------------------------
6+
// Rule Definition
7+
// ------------------------------------------------------------------------------
8+
9+
const DEFAULT_ALLOWED_PREFIXES = [
10+
'enforce',
11+
'require',
12+
'disallow',
13+
];
14+
15+
/**
16+
* Whether or not the node is a string literal
17+
*
18+
* @param {object} node
19+
* @returns {boolean} whether or not the node is a string literal
20+
*/
21+
function isStringLiteral (node) {
22+
return node.type === 'Literal' && typeof node.value === 'string';
23+
}
24+
25+
module.exports = {
26+
meta: {
27+
docs: {
28+
description: 'require rules to implement a meta.docs.description property with the correct format',
29+
category: 'Rules',
30+
recommended: false, // TODO: enable it in a major release.
31+
},
32+
type: 'suggestion',
33+
fixable: null,
34+
schema: [
35+
{
36+
type: 'object',
37+
properties: {
38+
allowedPrefixes: {
39+
type: 'array',
40+
items: {
41+
type: 'string',
42+
},
43+
},
44+
},
45+
additionalProperties: false,
46+
},
47+
],
48+
messages: {
49+
missing: '`meta.docs.description` is required.',
50+
wrongType: '`meta.docs.description` must be a non-empty string.',
51+
extraWhitespace: '`meta.docs.description` must not have leading nor trailing whitespace.',
52+
},
53+
},
54+
55+
create (context) {
56+
const sourceCode = context.getSourceCode();
57+
const info = utils.getRuleInfo(sourceCode.ast, sourceCode.scopeManager);
58+
59+
return {
60+
Program () {
61+
if (info === null || info.meta === null) {
62+
return;
63+
}
64+
65+
const allowedPrefixes = context.options[0] && context.options[0].allowedPrefixes ? context.options[0].allowedPrefixes : DEFAULT_ALLOWED_PREFIXES;
66+
67+
const metaNode = info.meta;
68+
const docsNode =
69+
metaNode &&
70+
metaNode.properties &&
71+
metaNode.properties.find(p => p.type === 'Property' && utils.getKeyName(p) === 'docs');
72+
73+
const descriptionNode =
74+
docsNode &&
75+
docsNode.value.properties &&
76+
docsNode.value.properties.find(p => p.type === 'Property' && utils.getKeyName(p) === 'description');
77+
78+
if (!descriptionNode) {
79+
context.report({ node: docsNode ? docsNode : metaNode, messageId: 'missing' });
80+
} else if (!isStringLiteral(descriptionNode.value) || descriptionNode.value.value === '') {
81+
context.report({ node: descriptionNode.value, messageId: 'wrongType' });
82+
} else if (descriptionNode.value.value !== descriptionNode.value.value.trim()) {
83+
context.report({ node: descriptionNode.value, messageId: 'extraWhitespace' });
84+
} else if (allowedPrefixes.length > 0 && allowedPrefixes.every(prefix => !descriptionNode.value.value.startsWith(prefix))) {
85+
context.report({
86+
node: descriptionNode.value,
87+
message: '`meta.docs.description` must start with an allowed prefix: {{prefixes}}.',
88+
data: { prefixes: allowedPrefixes.join(', ') },
89+
});
90+
}
91+
},
92+
};
93+
},
94+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
'use strict';
2+
3+
// ------------------------------------------------------------------------------
4+
// Requirements
5+
// ------------------------------------------------------------------------------
6+
7+
const rule = require('../../../lib/rules/require-meta-docs-description');
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-description', rule, {
16+
valid: [
17+
`
18+
module.exports = {
19+
meta: { docs: { description: 'disallow unused variables' } },
20+
create(context) {}
21+
};
22+
`,
23+
`
24+
module.exports = {
25+
meta: { docs: { description: 'enforce a maximum line length' } },
26+
create(context) {}
27+
};
28+
`,
29+
`
30+
module.exports = {
31+
meta: { docs: { description: 'require or disallow newline at the end of files' } },
32+
create(context) {}
33+
};
34+
`,
35+
{
36+
code:
37+
`
38+
module.exports = {
39+
meta: { docs: { description: 'myPrefix foo bar' } },
40+
create(context) {}
41+
};
42+
`,
43+
options: [{ allowedPrefixes: ['myPrefix'] }],
44+
},
45+
{
46+
code:
47+
`
48+
module.exports = {
49+
meta: { docs: { description: 'random message' } },
50+
create(context) {}
51+
};
52+
`,
53+
options: [{ allowedPrefixes: [] }], // any prefix allowed
54+
},
55+
],
56+
57+
invalid: [
58+
{
59+
code: `
60+
module.exports = {
61+
meta: {},
62+
create(context) {}
63+
};
64+
`,
65+
output: null,
66+
errors: [{ messageId: 'missing', type: 'ObjectExpression' }],
67+
},
68+
{
69+
code: `
70+
module.exports = {
71+
meta: { docs: {} },
72+
create(context) {}
73+
};
74+
`,
75+
output: null,
76+
errors: [{ messageId: 'missing', type: 'Property' }],
77+
},
78+
{
79+
code: `
80+
module.exports = {
81+
meta: { docs: { description: [] } },
82+
create(context) {}
83+
};
84+
`,
85+
output: null,
86+
errors: [{ messageId: 'wrongType', type: 'ArrayExpression' }],
87+
},
88+
{
89+
code: `
90+
module.exports = {
91+
meta: { docs: { description: \`enforce with template literal\` } },
92+
create(context) {}
93+
};
94+
`,
95+
output: null,
96+
errors: [{ messageId: 'wrongType', type: 'TemplateLiteral' }],
97+
},
98+
{
99+
code: `
100+
module.exports = {
101+
meta: { docs: { description: SOME_DESCRIPTION } },
102+
create(context) {}
103+
};
104+
`,
105+
output: null,
106+
errors: [{ messageId: 'wrongType', type: 'Identifier' }],
107+
},
108+
{
109+
code: `
110+
module.exports = {
111+
meta: { docs: { description: '' } },
112+
create(context) {}
113+
};
114+
`,
115+
output: null,
116+
errors: [{ messageId: 'wrongType', type: 'Literal' }],
117+
},
118+
{
119+
code: `
120+
module.exports = {
121+
meta: { docs: { description: 'enforce something with trailing whitespace ' } },
122+
create(context) {}
123+
};
124+
`,
125+
output: null,
126+
errors: [{ messageId: 'extraWhitespace', type: 'Literal' }],
127+
},
128+
{
129+
code: `
130+
module.exports = {
131+
meta: { docs: { description: 'this rule does ...' } },
132+
create(context) {}
133+
};
134+
`,
135+
output: null,
136+
errors: [{ message: '`meta.docs.description` must start with an allowed prefix: enforce, require, disallow.', type: 'Literal' }],
137+
},
138+
{
139+
code: `
140+
module.exports = {
141+
meta: { docs: { description: 'this rule does ...' } },
142+
create(context) {}
143+
};
144+
`,
145+
output: null,
146+
options: [{ allowedPrefixes: ['myPrefix'] }],
147+
errors: [{ message: '`meta.docs.description` must start with an allowed prefix: myPrefix.', type: 'Literal' }],
148+
},
149+
],
150+
});

0 commit comments

Comments
 (0)