Skip to content

Commit

Permalink
New: require-meta-docs-url (fixes #55) (#56)
Browse files Browse the repository at this point in the history
  • Loading branch information
mysticatea authored and not-an-aardvark committed Jan 14, 2018
1 parent c16bd2e commit 114d2c7
Show file tree
Hide file tree
Showing 5 changed files with 974 additions and 0 deletions.
1 change: 1 addition & 0 deletions .eslintrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ extends:
root: true
rules:
require-jsdoc: error
self/require-meta-docs-url: off
self/report-message-format:
- error
- '^[^a-z].*\.$'
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ Name | ✔️ | 🛠 | Description
[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
[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.
[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
[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
[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
[test-case-property-ordering](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/test-case-property-ordering.md) | | 🛠 | Requires the properties of a test case to be placed in a consistent order
[test-case-shorthand-strings](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/test-case-shorthand-strings.md) | | 🛠 | Enforce consistent usage of shorthand strings for test cases with no options
Expand Down
150 changes: 150 additions & 0 deletions docs/rules/require-meta-docs-url.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# require rules to implement a meta.docs.url property (require-meta-docs-url)

`meta.docs.url` property is the official location to store a URL to their documentation in the rule metadata.
Some integration tools will show the URL to users to understand rules.

## Rule Details

This rule aims to require ESLint rules to have a `meta.docs.url` property.

This rule has an option.

```json
{
"eslint-plugin/require-meta-docs-url": ["error", {
"pattern": "https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/{{name}}.md"
}]
}
```

- `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.

If you set the `pattern` option, this rule adds `meta.docs.url` property automatically when you execute `eslint --fix` command.

The following patterns are considered warnings:

```js

/* eslint eslint-plugin/require-meta-docs-url: "error" */

module.exports = {
meta: {},
create(context) {
}
};

```

```js

/* eslint eslint-plugin/require-meta-docs-url: "error" */

module.exports = {
meta: {
docs: {
url: undefined
}
},
create(context) {
}
};

```

```js

/* eslint eslint-plugin/require-meta-docs-url: ["error", {"pattern": "path/to/{{name}}.md"}] */

module.exports = {
meta: {
docs: {
url: "wrong URL"
}
},
create(context) {
}
};

```

The following patterns are not warnings:

```js

/* eslint eslint-plugin/require-meta-docs-url: "error" */

module.exports = {
meta: {
docs: {
url: "a URL"
}
},
create(context) {
}
};

```

```js

/* eslint eslint-plugin/require-meta-docs-url: ["error", {"pattern": "path/to/{{name}}.md"}] */

module.exports = {
meta: {
docs: {
url: "path/to/rule-name.md"
}
},
create(context) {
}
};

```

## Version specific URL

If you want to enforce version-specific URLs, it's feasible easily with `.eslintrc.js` and `npm version <type>` script.
For example:

**.eslintrc.js**:

```js
"use strict"

const version = require("./package.json").version

module.exports = {
plugins: ["eslint-plugin"],
// ... leaving out ...
rules: {
"eslint-plugin/require-meta-docs-url": ["error", {
pattern: `path/to/v${version}/docs/rules/{{name}}.md`,
}],
}
}
```

**package.json**:

```json
{
"version": "1.0.0",
"scripts": {
"pretest": "eslint .",
"test": "... leaving out ...",
"preversion": "npm test",
"version": "eslint . --fix && git add ."
},
// ... leaving out ...
}
```

Then `npm version <type>` command will update every rule to the new version's URL.

> npm runs `preversion` script on the current version, runs `version` script on the new version, and commits and makes a tag.
>
> Further reading: https://docs.npmjs.com/cli/version
## When Not To Use It

If you do not plan to provide rule's documentation in website, you can turn off this rule.
139 changes: 139 additions & 0 deletions lib/rules/require-meta-docs-url.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/**
* @author Toru Nagashima <https://github.com/mysticatea>
*/

'use strict';

// -----------------------------------------------------------------------------
// Requirements
// -----------------------------------------------------------------------------

const path = require('path');
const util = require('../utils');

// -----------------------------------------------------------------------------
// Rule Definition
// -----------------------------------------------------------------------------

module.exports = {
meta: {
docs: {
description: 'require rules to implement a meta.docs.url property',
category: 'Rules',
recommended: false,
},
fixable: 'code',
schema: [{
type: 'object',
properties: {
pattern: { type: 'string' },
},
additionalProperties: false,
}],
},

/**
* Creates AST event handlers for require-meta-docs-url.
* @param {RuleContext} context - The rule context.
* @returns {Object} AST event handlers.
*/
create (context) {
const options = context.options[0] || {};
const sourceCode = context.getSourceCode();
const filename = context.getFilename();
const ruleName = filename === '<input>' ? undefined : path.basename(filename, '.js');
const expectedUrl = !options.pattern || !ruleName
? undefined
: options.pattern.replace(/{{\s*name\s*}}/g, ruleName);

/**
* Check whether a given node is the expected URL.
* @param {Node} node The node of property value to check.
* @returns {boolean} `true` if the node is the expected URL.
*/
function isExpectedUrl (node) {
return Boolean(
node &&
node.type === 'Literal' &&
typeof node.value === 'string' &&
(
expectedUrl === undefined ||
node.value === expectedUrl
)
);
}

/**
* Insert a given property into a given object literal.
* @param {SourceCodeFixer} fixer The fixer.
* @param {Node} node The ObjectExpression node to insert a property.
* @param {string} propertyText The property code to insert.
* @returns {void}
*/
function insertProperty (fixer, node, propertyText) {
if (node.properties.length === 0) {
return fixer.replaceText(node, `{\n${propertyText}\n}`);
}
return fixer.insertTextAfter(
sourceCode.getLastToken(node.properties[node.properties.length - 1]),
`,\n${propertyText}`
);
}

return {
Program (node) {
const info = util.getRuleInfo(node);
if (info === null) {
return;
}

const metaNode = info.meta;
const docsPropNode =
metaNode &&
metaNode.properties &&
metaNode.properties.find(p => p.type === 'Property' && util.getKeyName(p) === 'docs');
const urlPropNode =
docsPropNode &&
docsPropNode.value.properties &&
docsPropNode.value.properties.find(p => p.type === 'Property' && util.getKeyName(p) === 'url');

if (isExpectedUrl(urlPropNode && urlPropNode.value)) {
return;
}

context.report({
loc:
(urlPropNode && urlPropNode.value.loc) ||
(docsPropNode && docsPropNode.value.loc) ||
(metaNode && metaNode.loc) ||
node.loc.start,

message:
!urlPropNode ? 'Rules should export a `meta.docs.url` property.' :
!expectedUrl ? '`meta.docs.url` property must be a string.' :
/* otherwise */ '`meta.docs.url` property must be `{{expectedUrl}}`.',

data: {
expectedUrl,
},

fix (fixer) {
if (expectedUrl) {
const urlString = JSON.stringify(expectedUrl);
if (urlPropNode) {
return fixer.replaceText(urlPropNode.value, urlString);
}
if (docsPropNode && docsPropNode.value.type === 'ObjectExpression') {
return insertProperty(fixer, docsPropNode.value, `url: ${urlString}`);
}
if (!docsPropNode && metaNode && metaNode.type === 'ObjectExpression') {
return insertProperty(fixer, metaNode, `docs: {\nurl: ${urlString}\n}`);
}
}
return null;
},
});
},
};
},
};
Loading

0 comments on commit 114d2c7

Please sign in to comment.