diff --git a/.eslintrc.yml b/.eslintrc.yml index ce7ec1da..92759fea 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -8,6 +8,7 @@ extends: root: true rules: require-jsdoc: error + self/meta-property-ordering: off self/require-meta-docs-url: off self/report-message-format: - error diff --git a/README.md b/README.md index 865b227d..a077de2a 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ 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 [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 diff --git a/docs/rules/meta-property-ordering.md b/docs/rules/meta-property-ordering.md new file mode 100644 index 00000000..29ce4c98 --- /dev/null +++ b/docs/rules/meta-property-ordering.md @@ -0,0 +1,66 @@ +# enforce ordering of meta properties in rule source (meta-property-ordering) + +(fixable) The `--fix` option on the [command line](../user-guide/command-line-interface#fix) automatically fixes problems reported by this rule. + +This rule enforces that meta properties of a rule are placed in a consistent order. + +## Rule Details + +### Options + +This rule has an array option: + +* `['type', 'docs', 'fixable', 'schema', 'messages', 'deprecated', 'replacedBy']` (default): The order that the properties of `meta` should be placed in. + +Examples of **incorrect** code for this rule: + +```js + +/* eslint eslint-plugin/meta-property-ordering: ["error", + ["type", "docs", "fixable", "schema", "messages"] +] */ + +// invalid; wrong order. +module.exports = { + meta: { + docs: "", + type: "problem", + fixable: "code", + }, + create() {}, +} + +// invalid; extra properties must be placed afterwards. +module.exports = { + meta: { + type: "problem", + fooooooooo: "foo", + docs: "", + fixable: "code", + }, + create() {}, +} +``` + +Examples of **correct** code for this rule: + +```js +/* eslint eslint-plugin/test-case-property-ordering: ["error", + ["type", "docs", "fixable", "schema", "messages"] +] */ + +// valid; +module.exports = { + meta: { + type: "bar", + docs: "foo", + messages: ["zoo"], + fooooooooo: "foo", + }, + create() {}, +} +``` + +## When Not To Use It + +If don't want to enforce ordering of meta properties, you can turn off this rule. diff --git a/lib/rules/meta-property-ordering.js b/lib/rules/meta-property-ordering.js new file mode 100644 index 00000000..3d563225 --- /dev/null +++ b/lib/rules/meta-property-ordering.js @@ -0,0 +1,88 @@ +/** + * @fileoverview Enforces the order of meta properties + */ + +'use strict'; + +const { getKeyName, getRuleInfo } = require('../utils'); + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +module.exports = { + meta: { + docs: { + description: 'Enforces the order of meta properties', + category: 'Rules', + recommended: false, + }, + type: 'suggestion', + fixable: 'code', + schema: [{ + type: 'array', + elements: { type: 'string' }, + }], + }, + + create (context) { + const sourceCode = context.getSourceCode(); + const info = getRuleInfo(sourceCode.ast); + + const message = 'The meta properties should be placed in a consistent order: [{{order}}].'; + const order = context.options[0] || ['type', 'docs', 'fixable', 'schema', 'messages']; + + const orderMap = new Map(order.map((name, i) => [name, i])); + + return { + Program () { + if ( + !info || + !info.meta || + info.meta.properties.length < 2 + ) { + return; + } + + const props = info.meta.properties; + + let last; + + const violatingProps = props.filter(prop => { + const curr = orderMap.has(getKeyName(prop)) + ? orderMap.get(getKeyName(prop)) + : Infinity; + return last > (last = curr); + }); + + if (violatingProps.length === 0) { + return; + } + + const knownProps = props + .filter(prop => orderMap.has(getKeyName(prop))) + .sort((a, b) => orderMap.get(getKeyName(a)) - orderMap.get(getKeyName(b))); + const unknownProps = props.filter(prop => !orderMap.has(getKeyName(prop))); + + for (const violatingProp of violatingProps) { + context.report({ + node: violatingProp, + message, + data: { + order: knownProps.map(getKeyName).join(', '), + }, + fix (fixer) { + const expectedProps = [...knownProps, ...unknownProps]; + return props.map((prop, k) => { + return fixer.replaceText( + prop, + sourceCode.getText(expectedProps[k]) + ); + }); + }, + }); + } + }, + }; + }, +}; diff --git a/tests/lib/rules/meta-property-ordering.js b/tests/lib/rules/meta-property-ordering.js new file mode 100644 index 00000000..c98cea63 --- /dev/null +++ b/tests/lib/rules/meta-property-ordering.js @@ -0,0 +1,133 @@ +/** + * @fileoverview Enforces the order of meta properties + */ + +'use strict'; + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +const rule = require('../../../lib/rules/meta-property-ordering'); +const RuleTester = require('eslint').RuleTester; + +// ------------------------------------------------------------------------------ +// Tests +// ------------------------------------------------------------------------------ + +/** + * @param {string[]} order + * @returns {string} + */ +function getMessage (order) { + return `The meta properties should be placed in a consistent order: [${order.join(', ')}].`; +} + +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } }); +ruleTester.run('test-case-property-ordering', rule, { + valid: [ + ` + module.exports = { + meta: {type, docs, fixable, schema, messages}, + create() {}, + };`, + + ` + module.exports = { + meta: {docs, schema, messages}, + create() {}, + };`, + + ` + module.exports = { + meta: {docs, messages, foo, bar}, + create() {}, + };`, + + ` + module.exports = { + meta: { + type: 'problem', + docs: {}, + fixable: 'code', + schema: [], + messages: {} + }, + create() {}, + };`, + { + code: ` + module.exports = { + meta: {schema, docs, fixable}, + create() {}, + };`, + options: [['schema', 'docs']], + }, + ` + module.exports = { + meta: {}, + create() {}, + };`, + ], + + invalid: [ + { + code: ` + module.exports = { + meta: { + docs, + fixable, + type: 'problem', + }, + create() {}, + };`, + + output: ` + module.exports = { + meta: { + type: 'problem', + docs, + fixable, + }, + create() {}, + };`, + errors: [{ message: getMessage(['type', 'docs', 'fixable']) }], + }, + { + code: ` + module.exports = { + meta: {schema, fixable, type, docs}, + create() {}, + };`, + + output: ` + module.exports = { + meta: {type, docs, fixable, schema}, + create() {}, + };`, + errors: [ + { message: getMessage(['type', 'docs', 'fixable', 'schema']) }, + { message: getMessage(['type', 'docs', 'fixable', 'schema']) }, + ], + }, + + { + code: ` + module.exports = { + meta: {fixable, fooooooooo, doc, type}, + create() {}, + };`, + + output: ` + module.exports = { + meta: {type, doc, fixable, fooooooooo}, + create() {}, + };`, + options: [['type', 'doc', 'fixable']], + errors: [ + { message: getMessage(['type', 'doc', 'fixable']) }, + { message: getMessage(['type', 'doc', 'fixable']) }, + ], + }, + ], +});