Repository: not-an-aardvark/eslint-plugin-eslint-plugin (js)

Issue:
New: meta-property-ordering
similar to rule test-case-property-ordering, but it aims to check the ordering rule meta.

not sure if it can enforce the exsistence of some properties (e.g. fixalbe, docs, messages), or another rule?

Pull request description:
New: meta-property-ordering (fixes #62)
fixes #62

Patch:
diff --git a/.eslintrc.yml b/.eslintrc.yml
index ce7ec1d..92759fe 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 865b227..a077de2 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 0000000..29ce4c9
--- /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 0000000..3d56322
--- /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])
+                );
+              });
+            },
+          });
+        }
+      },
+    };
+  },
+};


Test patch:
diff --git a/tests/lib/rules/meta-property-ordering.js b/tests/lib/rules/meta-property-ordering.js
new file mode 100644
index 0000000..c98cea6
--- /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']) },
+      ],
+    },
+  ],
+});