Skip to content

Commit ac04c01

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 38ad521 commit ac04c01

File tree

4 files changed

+239
-0
lines changed

4 files changed

+239
-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.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-type](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/require-meta-type.md) | | | require rules to implement a meta.type property
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# require rules to implement a meta.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.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+
## Further Reading
40+
41+
* [working-with-rules#options-schemas](https://eslint.org/docs/developer-guide/working-with-rules#options-schemas)
+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
'use strict';
2+
3+
const utils = require('../utils');
4+
5+
// ------------------------------------------------------------------------------
6+
// Rule Definition
7+
// ------------------------------------------------------------------------------
8+
9+
const ALLOWED_FIRST_WORDS = [
10+
'enforce',
11+
'require',
12+
'disallow',
13+
];
14+
15+
const ALLOWED_FIRST_WORDS_STRING = ALLOWED_FIRST_WORDS.map(word => `\`${word}\``).join(',');
16+
17+
/**
18+
* Whether or not the node is a string literal
19+
*
20+
* @param {object} node
21+
* @returns {boolean} whether or not the node is a string literal
22+
*/
23+
function isStringLiteral (node) {
24+
return node.type === 'Literal' && typeof node.value === 'string';
25+
}
26+
27+
module.exports = {
28+
meta: {
29+
docs: {
30+
description: 'require rules to implement a meta.description property with the correct format',
31+
category: 'Rules',
32+
recommended: false, // TODO: enable it in a major release.
33+
},
34+
type: 'suggestion',
35+
fixable: null,
36+
schema: [],
37+
messages: {
38+
missing: '`meta.description` is required.',
39+
wrongType: '`meta.description` must be a non-empty string.',
40+
extraWhitespace: '`meta.description` must not have leading nor trailing whitespace.',
41+
wrongPrefix: '`meta.description` must start with an allowed prefix: ' + ALLOWED_FIRST_WORDS_STRING,
42+
},
43+
},
44+
45+
create (context) {
46+
const sourceCode = context.getSourceCode();
47+
const info = utils.getRuleInfo(sourceCode.ast, sourceCode.scopeManager);
48+
49+
return {
50+
Program () {
51+
if (info === null || info.meta === null) {
52+
return;
53+
}
54+
55+
const metaNode = info.meta;
56+
const docsNode =
57+
metaNode &&
58+
metaNode.properties &&
59+
metaNode.properties.find(p => p.type === 'Property' && utils.getKeyName(p) === 'docs');
60+
61+
const descriptionNode =
62+
docsNode &&
63+
docsNode.value.properties &&
64+
docsNode.value.properties.find(p => p.type === 'Property' && utils.getKeyName(p) === 'description');
65+
66+
if (!descriptionNode) {
67+
context.report({ node: docsNode ? docsNode : metaNode, messageId: 'missing' });
68+
} else if (!isStringLiteral(descriptionNode.value) || descriptionNode.value.value === '') {
69+
context.report({ node: descriptionNode.value, messageId: 'wrongType' });
70+
} else if (descriptionNode.value.value !== descriptionNode.value.value.trim()) {
71+
context.report({ node: descriptionNode.value, messageId: 'extraWhitespace' });
72+
} else if (ALLOWED_FIRST_WORDS.every(word => !descriptionNode.value.value.startsWith(`${word} `))) {
73+
context.report({ node: descriptionNode.value, messageId: 'wrongPrefix' });
74+
}
75+
},
76+
};
77+
},
78+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
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+
37+
invalid: [
38+
{
39+
code: `
40+
module.exports = {
41+
meta: {},
42+
create(context) {}
43+
};
44+
`,
45+
output: null,
46+
errors: [{ messageId: 'missing', type: 'ObjectExpression' }],
47+
},
48+
{
49+
code: `
50+
module.exports = {
51+
meta: { docs: {} },
52+
create(context) {}
53+
};
54+
`,
55+
output: null,
56+
errors: [{ messageId: 'missing', type: 'Property' }],
57+
},
58+
{
59+
code: `
60+
module.exports = {
61+
meta: { docs: { description: [] } },
62+
create(context) {}
63+
};
64+
`,
65+
output: null,
66+
errors: [{ messageId: 'wrongType', type: 'ArrayExpression' }],
67+
},
68+
{
69+
code: `
70+
module.exports = {
71+
meta: { docs: { description: \`enforce with template literal\` } },
72+
create(context) {}
73+
};
74+
`,
75+
output: null,
76+
errors: [{ messageId: 'wrongType', type: 'TemplateLiteral' }],
77+
},
78+
{
79+
code: `
80+
module.exports = {
81+
meta: { docs: { description: SOME_DESCRIPTION } },
82+
create(context) {}
83+
};
84+
`,
85+
output: null,
86+
errors: [{ messageId: 'wrongType', type: 'Identifier' }],
87+
},
88+
{
89+
code: `
90+
module.exports = {
91+
meta: { docs: { description: '' } },
92+
create(context) {}
93+
};
94+
`,
95+
output: null,
96+
errors: [{ messageId: 'wrongType', type: 'Literal' }],
97+
},
98+
{
99+
code: `
100+
module.exports = {
101+
meta: { docs: { description: 'enforce something with trailing whitespace ' } },
102+
create(context) {}
103+
};
104+
`,
105+
output: null,
106+
errors: [{ messageId: 'extraWhitespace', type: 'Literal' }],
107+
},
108+
{
109+
code: `
110+
module.exports = {
111+
meta: { docs: { description: 'this rule does ...' } },
112+
create(context) {}
113+
};
114+
`,
115+
output: null,
116+
errors: [{ messageId: 'wrongPrefix', type: 'Literal' }],
117+
},
118+
],
119+
});

0 commit comments

Comments
 (0)