Skip to content

Commit a8abb11

Browse files
committed
New: require-meta-docs-url (fixes #55)
1 parent c16bd2e commit a8abb11

File tree

3 files changed

+923
-0
lines changed

3 files changed

+923
-0
lines changed

docs/rules/require-meta-docs-url.md

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# require rules to implement a meta.docs.url property (require-meta-docs-url)
2+
3+
`meta.docs.url` property is the official location to store a URL to their documentation in the rule metadata.
4+
Some integration tools will show the URL to users to understand rules.
5+
6+
## Rule Details
7+
8+
This rule aims to require ESLint rules to have a `meta.docs.url` property.
9+
10+
This rule has an option.
11+
12+
```json
13+
{
14+
"eslint-plugin/require-meta-docs-url": ["error", {
15+
"pattern": "https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/{{name}}.md"
16+
}]
17+
}
18+
```
19+
20+
- `pattern` (`string`) ... A pattern to enforce rule's document URL. It replaces `{{name}}` placeholder by each rule name. The rule name is the basename of each rule file. Default is undefined.
21+
22+
If you set the `pattern` option, this rule adds `meta.docs.url` property automatically when you executed `eslint --fix` command.
23+
24+
The following patterns are considered warnings:
25+
26+
```js
27+
28+
/* eslint eslint-plugin/require-meta-docs-url: "error" */
29+
30+
module.exports = {
31+
meta: {},
32+
create(context) {
33+
}
34+
};
35+
36+
```
37+
38+
```js
39+
40+
/* eslint eslint-plugin/require-meta-docs-url: "error" */
41+
42+
module.exports = {
43+
meta: {
44+
docs: {
45+
url: undefined
46+
}
47+
},
48+
create(context) {
49+
}
50+
};
51+
52+
```
53+
54+
```js
55+
56+
/* eslint eslint-plugin/require-meta-docs-url: ["error", {"pattern": "path/to/{{name}}.md"}] */
57+
58+
module.exports = {
59+
meta: {
60+
docs: {
61+
url: "wrong URL"
62+
}
63+
},
64+
create(context) {
65+
}
66+
};
67+
68+
```
69+
70+
The following patterns are not warnings:
71+
72+
```js
73+
74+
/* eslint eslint-plugin/require-meta-docs-url: "error" */
75+
76+
module.exports = {
77+
meta: {
78+
docs: {
79+
url: "a URL"
80+
}
81+
},
82+
create(context) {
83+
}
84+
};
85+
86+
```
87+
88+
```js
89+
90+
/* eslint eslint-plugin/require-meta-docs-url: ["error", {"pattern": "path/to/{{name}}.md"}] */
91+
92+
module.exports = {
93+
meta: {
94+
docs: {
95+
url: "path/to/rule-name.md"
96+
}
97+
},
98+
create(context) {
99+
}
100+
};
101+
102+
```
103+
104+
## When Not To Use It
105+
106+
If you do not plan to provide rule's documentation in website, you can turn off this rule.

lib/rules/require-meta-docs-url.js

+135
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
/**
2+
* @author Toru Nagashima <https://github.com/mysticatea>
3+
*/
4+
5+
'use strict';
6+
7+
// -----------------------------------------------------------------------------
8+
// Requirements
9+
// -----------------------------------------------------------------------------
10+
11+
const path = require('path');
12+
const util = require('../utils');
13+
14+
// -----------------------------------------------------------------------------
15+
// Rule Definition
16+
// -----------------------------------------------------------------------------
17+
18+
module.exports = {
19+
meta: {
20+
docs: {
21+
description: 'require rules to implement a meta.docs.url property',
22+
category: 'Rules',
23+
recommended: false,
24+
},
25+
fixable: 'code',
26+
schema: [{
27+
type: 'object',
28+
properties: {
29+
pattern: { type: 'string' },
30+
},
31+
additionalProperties: false,
32+
}],
33+
},
34+
35+
/**
36+
* Creates AST event handlers for require-meta-docs-url.
37+
* @param {RuleContext} context - The rule context.
38+
* @returns {Object} AST event handlers.
39+
*/
40+
create (context) {
41+
const options = context.options[0] || {};
42+
const sourceCode = context.getSourceCode();
43+
const filename = context.getFilename();
44+
const ruleName = filename === '<input>' ? undefined : path.basename(filename, '.js');
45+
const expectedUrl = !options.pattern || !ruleName
46+
? undefined
47+
: options.pattern.replace(/{{\s*name\s*}}/g, ruleName);
48+
49+
/**
50+
* Check whether a given node is the expected URL.
51+
* @param {Node} node The node of property value to check.
52+
* @returns {boolean} `true` if the node is the expected URL.
53+
*/
54+
function isExpectedUrl (node) {
55+
return Boolean(
56+
node &&
57+
node.type === 'Literal' &&
58+
typeof node.value === 'string' &&
59+
(
60+
expectedUrl === undefined ||
61+
node.value === expectedUrl
62+
)
63+
);
64+
}
65+
66+
/**
67+
* Insert a given property into a given object literal.
68+
* @param {SourceCodeFixer} fixer The fixer.
69+
* @param {Node} node The ObjectExpression node to insert a property.
70+
* @param {string} propertyText The property code to insert.
71+
* @returns {void}
72+
*/
73+
function insertProperty (fixer, node, propertyText) {
74+
if (node.properties.length === 0) {
75+
return fixer.replaceText(node, `{\n${propertyText}\n}`);
76+
}
77+
return fixer.insertTextAfter(
78+
sourceCode.getLastToken(node.properties[node.properties.length - 1]),
79+
`,\n${propertyText}`
80+
);
81+
}
82+
83+
return {
84+
Program (node) {
85+
const info = util.getRuleInfo(node);
86+
const metaNode = info && info.meta;
87+
const docsPropNode =
88+
metaNode &&
89+
metaNode.properties &&
90+
metaNode.properties.find(p => p.type === 'Property' && util.getKeyName(p) === 'docs');
91+
const urlPropNode =
92+
docsPropNode &&
93+
docsPropNode.value.properties &&
94+
docsPropNode.value.properties.find(p => p.type === 'Property' && util.getKeyName(p) === 'url');
95+
96+
if (isExpectedUrl(urlPropNode && urlPropNode.value)) {
97+
return;
98+
}
99+
100+
context.report({
101+
loc:
102+
(urlPropNode && urlPropNode.value.loc) ||
103+
(docsPropNode && docsPropNode.value.loc) ||
104+
(metaNode && metaNode.loc) ||
105+
node.loc.start,
106+
107+
message:
108+
!urlPropNode ? 'Rules should export a `meta.docs.url` property.' :
109+
!expectedUrl ? '`meta.docs.url` property must be a string.' :
110+
/* otherwise */ '`meta.docs.url` property must be `{{expectedUrl}}`.',
111+
112+
data: {
113+
expectedUrl,
114+
},
115+
116+
fix (fixer) {
117+
if (expectedUrl) {
118+
const urlString = JSON.stringify(expectedUrl);
119+
if (urlPropNode) {
120+
return fixer.replaceText(urlPropNode.value, urlString);
121+
}
122+
if (docsPropNode && docsPropNode.value.type === 'ObjectExpression') {
123+
return insertProperty(fixer, docsPropNode.value, `url: ${urlString}`);
124+
}
125+
if (!docsPropNode && metaNode && metaNode.type === 'ObjectExpression') {
126+
return insertProperty(fixer, metaNode, `docs: {\nurl: ${urlString}\n}`);
127+
}
128+
}
129+
return null;
130+
},
131+
});
132+
},
133+
};
134+
},
135+
};

0 commit comments

Comments
 (0)