diff --git a/README.md b/README.md index e068161a..c1eb4711 100644 --- a/README.md +++ b/README.md @@ -49,25 +49,26 @@ Then configure the rules you want to use under the rules section. Name | ✔️ | 🛠 | Description ----- | ----- | ----- | ----- -[consistent-output](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/consistent-output.md) | | | Enforce consistent use of output assertions in rule tests -[fixer-return](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/fixer-return.md) | ✔️ | | Expected fixer function to always return a value. -[meta-property-ordering](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/meta-property-ordering.md) | | 🛠 | Enforces the order of meta properties -[no-deprecated-context-methods](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/no-deprecated-context-methods.md) | | 🛠 | Disallows usage of deprecated methods on rule context objects +[consistent-output](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/consistent-output.md) | | | enforce consistent use of output assertions in rule tests +[fixer-return](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/fixer-return.md) | ✔️ | | require fixer function to always return a value. +[meta-property-ordering](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/meta-property-ordering.md) | | 🛠 | enforce the order of meta properties +[no-deprecated-context-methods](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/no-deprecated-context-methods.md) | | 🛠 | disallow usage of deprecated methods on rule context objects [no-deprecated-report-api](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/no-deprecated-report-api.md) | ✔️ | 🛠 | disallow use of the deprecated context.report() API [no-identical-tests](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/no-identical-tests.md) | ✔️ | 🛠 | disallow identical tests -[no-missing-placeholders](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/no-missing-placeholders.md) | ✔️ | | Disallow missing placeholders in rule report messages -[no-unused-placeholders](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/no-unused-placeholders.md) | ✔️ | | Disallow unused placeholders in rule report messages -[no-useless-token-range](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/no-useless-token-range.md) | ✔️ | 🛠 | Disallow unnecessary calls to sourceCode.getFirstToken and sourceCode.getLastToken -[prefer-output-null](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/prefer-output-null.md) | | 🛠 | disallows invalid RuleTester test cases with the output the same as the code. +[no-missing-placeholders](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/no-missing-placeholders.md) | ✔️ | | disallow missing placeholders in rule report messages +[no-unused-placeholders](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/no-unused-placeholders.md) | ✔️ | | disallow unused placeholders in rule report messages +[no-useless-token-range](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/no-useless-token-range.md) | ✔️ | 🛠 | disallow unnecessary calls to sourceCode.getFirstToken and sourceCode.getLastToken +[prefer-output-null](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/prefer-output-null.md) | | 🛠 | disallow invalid RuleTester test cases with the output the same as the code. [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. +[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. [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-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 [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 [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 [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 -[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 +[test-case-property-ordering](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/test-case-property-ordering.md) | | 🛠 | require 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 ## Supported Presets diff --git a/docs/rules/require-meta-docs-description.md b/docs/rules/require-meta-docs-description.md new file mode 100644 index 00000000..24fa29d7 --- /dev/null +++ b/docs/rules/require-meta-docs-description.md @@ -0,0 +1,47 @@ +# require rules to implement a meta.docs.description property (require-meta-docs-description) + +Defining a clear and consistent description for each rule helps developers understand what they're used for. + +In particular, each rule description should begin with an allowed prefix: +* `enforce` +* `require` +* `disallow` + +## Rule Details + +This rule requires ESLint rules to have a valid `meta.docs.description` property. + +Examples of **incorrect** code for this rule: + +```js +/* eslint eslint-plugin/require-meta-docs-description: error */ +module.exports = { + meta: {}, + create: function(context) { /* ... */} +}; + +module.exports = { + meta: { description: 'this rule does ...' }, // missing allowed prefix + create: function(context) { /* ... */} +}; +``` + +Examples of **correct** code for this rule: + +```js +/* eslint eslint-plugin/require-meta-docs-description: error */ +module.exports = { + meta: { description: 'disallow unused variables' }, + create: function(context) { /* ... */} +}; +``` + +## Options + +This rule takes an optional object containing: + +- `String` — `pattern` — A regular expression that the description must match. Use `'.+'` to allow anything. Defaults to `^(enforce|require|disallow)`. + +## Further Reading + +* [working-with-rules#options-schemas](https://eslint.org/docs/developer-guide/working-with-rules#options-schemas) diff --git a/lib/rules/consistent-output.js b/lib/rules/consistent-output.js index 97bd4553..35478e76 100644 --- a/lib/rules/consistent-output.js +++ b/lib/rules/consistent-output.js @@ -14,7 +14,7 @@ const utils = require('../utils'); module.exports = { meta: { docs: { - description: 'Enforce consistent use of output assertions in rule tests', + description: 'enforce consistent use of output assertions in rule tests', category: 'Tests', recommended: false, }, diff --git a/lib/rules/fixer-return.js b/lib/rules/fixer-return.js index 6975d98e..8d4898c1 100644 --- a/lib/rules/fixer-return.js +++ b/lib/rules/fixer-return.js @@ -18,7 +18,7 @@ const utils = require('../utils'); module.exports = { meta: { docs: { - description: 'Expected fixer function to always return a value.', + description: 'require fixer function to always return a value.', category: 'Possible Errors', recommended: true, }, diff --git a/lib/rules/meta-property-ordering.js b/lib/rules/meta-property-ordering.js index 3d563225..6fb67a59 100644 --- a/lib/rules/meta-property-ordering.js +++ b/lib/rules/meta-property-ordering.js @@ -13,7 +13,7 @@ const { getKeyName, getRuleInfo } = require('../utils'); module.exports = { meta: { docs: { - description: 'Enforces the order of meta properties', + description: 'enforce the order of meta properties', category: 'Rules', recommended: false, }, diff --git a/lib/rules/no-deprecated-context-methods.js b/lib/rules/no-deprecated-context-methods.js index 32f73645..27c5afc6 100644 --- a/lib/rules/no-deprecated-context-methods.js +++ b/lib/rules/no-deprecated-context-methods.js @@ -37,7 +37,7 @@ const DEPRECATED_PASSTHROUGHS = { module.exports = { meta: { docs: { - description: 'Disallows usage of deprecated methods on rule context objects', + description: 'disallow usage of deprecated methods on rule context objects', category: 'Rules', recommended: false, }, diff --git a/lib/rules/no-missing-placeholders.js b/lib/rules/no-missing-placeholders.js index 15dd8986..c4cf742e 100644 --- a/lib/rules/no-missing-placeholders.js +++ b/lib/rules/no-missing-placeholders.js @@ -14,7 +14,7 @@ const utils = require('../utils'); module.exports = { meta: { docs: { - description: 'Disallow missing placeholders in rule report messages', + description: 'disallow missing placeholders in rule report messages', category: 'Rules', recommended: true, }, diff --git a/lib/rules/no-unused-placeholders.js b/lib/rules/no-unused-placeholders.js index 194a9d73..af7b78d1 100644 --- a/lib/rules/no-unused-placeholders.js +++ b/lib/rules/no-unused-placeholders.js @@ -14,7 +14,7 @@ const utils = require('../utils'); module.exports = { meta: { docs: { - description: 'Disallow unused placeholders in rule report messages', + description: 'disallow unused placeholders in rule report messages', category: 'Rules', recommended: true, }, diff --git a/lib/rules/no-useless-token-range.js b/lib/rules/no-useless-token-range.js index 1461f79b..ce6f5b6d 100644 --- a/lib/rules/no-useless-token-range.js +++ b/lib/rules/no-useless-token-range.js @@ -14,7 +14,7 @@ const utils = require('../utils'); module.exports = { meta: { docs: { - description: 'Disallow unnecessary calls to sourceCode.getFirstToken and sourceCode.getLastToken', + description: 'disallow unnecessary calls to sourceCode.getFirstToken and sourceCode.getLastToken', category: 'Rules', recommended: true, }, diff --git a/lib/rules/prefer-output-null.js b/lib/rules/prefer-output-null.js index 89a47aac..8e6c6306 100644 --- a/lib/rules/prefer-output-null.js +++ b/lib/rules/prefer-output-null.js @@ -14,7 +14,7 @@ const utils = require('../utils'); module.exports = { meta: { docs: { - description: 'disallows invalid RuleTester test cases with the output the same as the code.', + description: 'disallow invalid RuleTester test cases with the output the same as the code.', category: 'Tests', recommended: false, }, diff --git a/lib/rules/prefer-replace-text.js b/lib/rules/prefer-replace-text.js index 69bc0a05..3b7278b2 100644 --- a/lib/rules/prefer-replace-text.js +++ b/lib/rules/prefer-replace-text.js @@ -14,7 +14,7 @@ const utils = require('../utils'); module.exports = { meta: { docs: { - description: 'prefer using replaceText instead of replaceTextRange.', + description: 'require using replaceText instead of replaceTextRange.', category: 'Rules', recommended: false, }, diff --git a/lib/rules/require-meta-docs-description.js b/lib/rules/require-meta-docs-description.js new file mode 100644 index 00000000..dbf7e329 --- /dev/null +++ b/lib/rules/require-meta-docs-description.js @@ -0,0 +1,87 @@ +'use strict'; + +const utils = require('../utils'); + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const DEFAULT_PATTERN = new RegExp('^(enforce|require|disallow)'); + +/** + * Whether or not the node is a string literal + * + * @param {object} node + * @returns {boolean} whether or not the node is a string literal + */ +function isStringLiteral (node) { + return node.type === 'Literal' && typeof node.value === 'string'; +} + +module.exports = { + meta: { + docs: { + description: 'require rules to implement a meta.docs.description property with the correct format', + category: 'Rules', + recommended: false, // TODO: enable it in a major release. + }, + type: 'suggestion', + fixable: null, + schema: [ + { + type: 'object', + properties: { + pattern: { + type: 'string', + }, + }, + additionalProperties: false, + }, + ], + messages: { + missing: '`meta.docs.description` is required.', + wrongType: '`meta.docs.description` must be a non-empty string.', + extraWhitespace: '`meta.docs.description` must not have leading nor trailing whitespace.', + }, + }, + + create (context) { + const sourceCode = context.getSourceCode(); + const info = utils.getRuleInfo(sourceCode.ast, sourceCode.scopeManager); + + return { + Program () { + if (info === null || info.meta === null) { + return; + } + + const pattern = context.options[0] && context.options[0].pattern ? new RegExp(context.options[0].pattern) : DEFAULT_PATTERN; + + const metaNode = info.meta; + const docsNode = + metaNode && + metaNode.properties && + metaNode.properties.find(p => p.type === 'Property' && utils.getKeyName(p) === 'docs'); + + const descriptionNode = + docsNode && + docsNode.value.properties && + docsNode.value.properties.find(p => p.type === 'Property' && utils.getKeyName(p) === 'description'); + + if (!descriptionNode) { + context.report({ node: docsNode ? docsNode : metaNode, messageId: 'missing' }); + } else if (!isStringLiteral(descriptionNode.value) || descriptionNode.value.value === '') { + context.report({ node: descriptionNode.value, messageId: 'wrongType' }); + } else if (descriptionNode.value.value !== descriptionNode.value.value.trim()) { + context.report({ node: descriptionNode.value, messageId: 'extraWhitespace' }); + } else if (!pattern.test(descriptionNode.value.value)) { + context.report({ + node: descriptionNode.value, + message: '`meta.docs.description` must match the regexp {{pattern}}.', + data: { pattern }, + }); + } + }, + }; + }, +}; diff --git a/lib/rules/test-case-property-ordering.js b/lib/rules/test-case-property-ordering.js index 5a8280a9..f5778bda 100644 --- a/lib/rules/test-case-property-ordering.js +++ b/lib/rules/test-case-property-ordering.js @@ -14,7 +14,7 @@ const utils = require('../utils'); module.exports = { meta: { docs: { - description: 'Requires the properties of a test case to be placed in a consistent order', + description: 'require the properties of a test case to be placed in a consistent order', category: 'Tests', recommended: false, }, diff --git a/lib/rules/test-case-shorthand-strings.js b/lib/rules/test-case-shorthand-strings.js index 9b46a248..edec6d20 100644 --- a/lib/rules/test-case-shorthand-strings.js +++ b/lib/rules/test-case-shorthand-strings.js @@ -14,7 +14,7 @@ const utils = require('../utils'); module.exports = { meta: { docs: { - description: 'Enforce consistent usage of shorthand strings for test cases with no options', + description: 'enforce consistent usage of shorthand strings for test cases with no options', category: 'Tests', recommended: false, }, diff --git a/tests/lib/rules/require-meta-docs-description.js b/tests/lib/rules/require-meta-docs-description.js new file mode 100644 index 00000000..bb5ef8f5 --- /dev/null +++ b/tests/lib/rules/require-meta-docs-description.js @@ -0,0 +1,150 @@ +'use strict'; + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +const rule = require('../../../lib/rules/require-meta-docs-description'); +const RuleTester = require('eslint').RuleTester; + +// ------------------------------------------------------------------------------ +// Tests +// ------------------------------------------------------------------------------ + +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } }); +ruleTester.run('require-meta-docs-description', rule, { + valid: [ + ` + module.exports = { + meta: { docs: { description: 'disallow unused variables' } }, + create(context) {} + }; + `, + ` + module.exports = { + meta: { docs: { description: 'enforce a maximum line length' } }, + create(context) {} + }; + `, + ` + module.exports = { + meta: { docs: { description: 'require or disallow newline at the end of files' } }, + create(context) {} + }; + `, + { + code: + ` + module.exports = { + meta: { docs: { description: 'myPrefix foo bar' } }, + create(context) {} + }; + `, + options: [{ pattern: '^myPrefix' }], + }, + { + code: + ` + module.exports = { + meta: { docs: { description: 'random message' } }, + create(context) {} + }; + `, + options: [{ pattern: '.+' }], // any description allowed + }, + ], + + invalid: [ + { + code: ` + module.exports = { + meta: {}, + create(context) {} + }; + `, + output: null, + errors: [{ messageId: 'missing', type: 'ObjectExpression' }], + }, + { + code: ` + module.exports = { + meta: { docs: {} }, + create(context) {} + }; + `, + output: null, + errors: [{ messageId: 'missing', type: 'Property' }], + }, + { + code: ` + module.exports = { + meta: { docs: { description: [] } }, + create(context) {} + }; + `, + output: null, + errors: [{ messageId: 'wrongType', type: 'ArrayExpression' }], + }, + { + code: ` + module.exports = { + meta: { docs: { description: \`enforce with template literal\` } }, + create(context) {} + }; + `, + output: null, + errors: [{ messageId: 'wrongType', type: 'TemplateLiteral' }], + }, + { + code: ` + module.exports = { + meta: { docs: { description: SOME_DESCRIPTION } }, + create(context) {} + }; + `, + output: null, + errors: [{ messageId: 'wrongType', type: 'Identifier' }], + }, + { + code: ` + module.exports = { + meta: { docs: { description: '' } }, + create(context) {} + }; + `, + output: null, + errors: [{ messageId: 'wrongType', type: 'Literal' }], + }, + { + code: ` + module.exports = { + meta: { docs: { description: 'enforce something with trailing whitespace ' } }, + create(context) {} + }; + `, + output: null, + errors: [{ messageId: 'extraWhitespace', type: 'Literal' }], + }, + { + code: ` + module.exports = { + meta: { docs: { description: 'this rule does ...' } }, + create(context) {} + }; + `, + output: null, + errors: [{ message: '`meta.docs.description` must match the regexp /^(enforce|require|disallow)/.', type: 'Literal' }], + }, + { + code: ` + module.exports = { + meta: { docs: { description: 'this rule does ...' } }, + create(context) {} + }; + `, + output: null, + options: [{ pattern: '^myPrefix' }], + errors: [{ message: '`meta.docs.description` must match the regexp /^myPrefix/.', type: 'Literal' }], + }, + ], +});