Repository: not-an-aardvark/eslint-plugin-eslint-plugin
Pull request #258 (2022-07-07 16:58:46)

breaking: add `recommended` rules `prefer-message-ids`, `prefer-output-null`, `no-missing-message-ids`, `no-unused-message-ids`
Enable additional `recommended` rules:
   * [prefer-message-ids](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/main/docs/rules/prefer-message-ids.md): message IDs have become best practice, ESLint core enforces this rule already
   * [prefer-output-null](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/main/docs/rules/prefer-output-null.md): this will enforce upcoming ESLint v9 behavior from an approved [RFC](https://github.com/eslint/rfcs/tree/main/designs/2021-stricter-rule-test-validation#autofix-and-suggestion-output-must-differ-from-the-test-case-code)
   * https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/pull/254

Remove from `recommended`:
* ~~[consistent-output](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/main/docs/rules/consistent-output.md): no longer necessary since ESLint v7 [validates](https://eslint.org/docs/user-guide/migrating-to-7.0.0#rule-tester-strict) that test cases provide `output` when they produce an autofix, and it's tedious to include `output: null` when not necessary, so we shouldn't require it~~

Part of v5 release (https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/issues/230).


aladdin-add: > [consistent-output](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/main/docs/rules/consistent-output.md): no longer necessary since ESLint v7 [validates](https://eslint.org/docs/user-guide/migrating-to-7.0.0#rule-tester-strict) that test cases provide output when they produce an autofix, and it's tedious to include output: null when not necessary, so we shouldn't require it.

it's no harm not to remove it - linting can find the error earilier?

Patch:
diff --git a/.npmpackagejsonlintrc.json b/.npmpackagejsonlintrc.json
new file mode 100644
index 0000000..40af081
--- /dev/null
+++ b/.npmpackagejsonlintrc.json
@@ -0,0 +1,15 @@
+{
+  "rules": {
+    "no-duplicate-properties": "error",
+    "no-repeated-dependencies": "error",
+    "prefer-alphabetical-bundledDependencies": "error",
+    "prefer-alphabetical-dependencies": "error",
+    "prefer-alphabetical-devDependencies": "error",
+    "prefer-alphabetical-optionalDependencies": "error",
+    "prefer-alphabetical-scripts": "error",
+    "prefer-caret-version-dependencies": "error",
+    "prefer-caret-version-devDependencies": ["error", {
+      "exceptions": ["eslint-plugin-eslint-plugin"]
+    }]
+  }
+}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 29bc59e..1e7858f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
 
 
+### [4.4.1](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/compare/v4.4.0...v4.4.1) (2022-07-11)
+
+
+### Bug Fixes
+
+* detect rules with TypeScript `export = rule` syntax ([#267](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/issues/267)) ([8e66bf7](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/commit/8e66bf744448b8a3c64bf32c5ba893d16fa7f3ac))
+
 ## [4.4.0](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/compare/v4.3.0...v4.4.0) (2022-07-02)
 
 
diff --git a/README.md b/README.md
index f9c2cd5..59f09c6 100644
--- a/README.md
+++ b/README.md
@@ -67,15 +67,15 @@ Name | ✔️ | 🛠 | 💡 | Description
 [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 the version of `context.report()` with multiple arguments
 [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-message-ids](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/no-missing-message-ids.md) |  |  |  | disallow `messageId`s that are missing from `meta.messages`
+[no-missing-message-ids](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/no-missing-message-ids.md) | ✔️ |  |  | disallow `messageId`s that are missing from `meta.messages`
 [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-only-tests](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/no-only-tests.md) | ✔️ |  | 💡 | disallow the test case property `only`
-[no-unused-message-ids](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/no-unused-message-ids.md) |  |  |  | disallow unused `messageId`s in `meta.messages`
+[no-unused-message-ids](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/no-unused-message-ids.md) | ✔️ |  |  | disallow unused `messageId`s in `meta.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-message-ids](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/prefer-message-ids.md) |  |  |  | require using `messageId` instead of `message` to report rule violations
+[prefer-message-ids](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/prefer-message-ids.md) | ✔️ |  |  | require using `messageId` instead of `message` to report rule violations
 [prefer-object-rule](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/prefer-object-rule.md) | ✔️ | 🛠 |  | disallow function-style rules
-[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 where the `output` matches the `code`
+[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 where the `output` matches the `code`
 [prefer-placeholders](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/prefer-placeholders.md) |  |  |  | require using placeholders for dynamic report messages
 [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
diff --git a/docs/rules/no-missing-message-ids.md b/docs/rules/no-missing-message-ids.md
index 76ee359..268c42b 100644
--- a/docs/rules/no-missing-message-ids.md
+++ b/docs/rules/no-missing-message-ids.md
@@ -1,5 +1,7 @@
 # Disallow `messageId`s that are missing from `meta.messages` (no-missing-message-ids)
 
+✔️ The `"extends": "plugin:eslint-plugin/recommended"` property in a configuration file enables this rule.
+
 When using `meta.messages` and `messageId` to report rule violations, it's possible to mistakenly use a `messageId` that doesn't exist in `meta.messages`.
 
 ## Rule Details
diff --git a/docs/rules/no-unused-message-ids.md b/docs/rules/no-unused-message-ids.md
index 758140a..942d33d 100644
--- a/docs/rules/no-unused-message-ids.md
+++ b/docs/rules/no-unused-message-ids.md
@@ -1,5 +1,7 @@
 # Disallow unused `messageId`s in `meta.messages` (no-unused-message-ids)
 
+✔️ The `"extends": "plugin:eslint-plugin/recommended"` property in a configuration file enables this rule.
+
 When using `meta.messages` and `messageId` to report rule violations, it's possible to mistakenly leave a message in `meta.messages` that is never used.
 
 ## Rule Details
diff --git a/docs/rules/prefer-message-ids.md b/docs/rules/prefer-message-ids.md
index 4455df2..4f1696c 100644
--- a/docs/rules/prefer-message-ids.md
+++ b/docs/rules/prefer-message-ids.md
@@ -1,5 +1,7 @@
 # Require using `messageId` instead of `message` to report rule violations (prefer-message-ids)
 
+✔️ The `"extends": "plugin:eslint-plugin/recommended"` property in a configuration file enables this rule.
+
 When reporting a rule violation, it's preferred to provide the violation message with the `messageId` property instead of the `message` property. Message IDs provide the following benefits:
 
 * Rule violation messages can be stored in a central `meta.messages` object for convenient management
diff --git a/docs/rules/prefer-output-null.md b/docs/rules/prefer-output-null.md
index 31d6ceb..e5e0a5d 100644
--- a/docs/rules/prefer-output-null.md
+++ b/docs/rules/prefer-output-null.md
@@ -1,5 +1,7 @@
 # Disallow invalid RuleTester test cases where the `output` matches the `code` (prefer-output-null)
 
+✔️ The `"extends": "plugin:eslint-plugin/recommended"` property in a configuration file enables this rule.
+
 ⚒️ The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#--fix) can automatically fix some of the problems reported by this rule.
 
 Instead of repeating the test case `code`, using `output: null` is more concise and makes it easier to distinguish whether a test case provides an autofix.
diff --git a/lib/rules/no-missing-message-ids.js b/lib/rules/no-missing-message-ids.js
index f10c950..47cabd7 100644
--- a/lib/rules/no-missing-message-ids.js
+++ b/lib/rules/no-missing-message-ids.js
@@ -14,7 +14,7 @@ module.exports = {
       description:
         'disallow `messageId`s that are missing from `meta.messages`',
       category: 'Rules',
-      recommended: false,
+      recommended: true,
       url: 'https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/tree/HEAD/docs/rules/no-missing-message-ids.md',
     },
     fixable: null,
diff --git a/lib/rules/no-unused-message-ids.js b/lib/rules/no-unused-message-ids.js
index a1724a6..df79360 100644
--- a/lib/rules/no-unused-message-ids.js
+++ b/lib/rules/no-unused-message-ids.js
@@ -13,7 +13,7 @@ module.exports = {
     docs: {
       description: 'disallow unused `messageId`s in `meta.messages`',
       category: 'Rules',
-      recommended: false,
+      recommended: true,
       url: 'https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/tree/HEAD/docs/rules/no-unused-message-ids.md',
     },
     fixable: null,
diff --git a/lib/rules/prefer-message-ids.js b/lib/rules/prefer-message-ids.js
index 9fca98a..8d80860 100644
--- a/lib/rules/prefer-message-ids.js
+++ b/lib/rules/prefer-message-ids.js
@@ -15,7 +15,7 @@ module.exports = {
       description:
         'require using `messageId` instead of `message` to report rule violations',
       category: 'Rules',
-      recommended: false,
+      recommended: true,
       url: 'https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/tree/HEAD/docs/rules/prefer-message-ids.md',
     },
     fixable: null,
diff --git a/lib/rules/prefer-output-null.js b/lib/rules/prefer-output-null.js
index 87fe877..0716d91 100644
--- a/lib/rules/prefer-output-null.js
+++ b/lib/rules/prefer-output-null.js
@@ -19,7 +19,7 @@ module.exports = {
       description:
         'disallow invalid RuleTester test cases where the `output` matches the `code`',
       category: 'Tests',
-      recommended: false,
+      recommended: true,
       url: 'https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/tree/HEAD/docs/rules/prefer-output-null.md',
     },
     fixable: 'code',
diff --git a/lib/utils.js b/lib/utils.js
index 5b74496..d2bc650 100644
--- a/lib/utils.js
+++ b/lib/utils.js
@@ -121,8 +121,13 @@ function isTypeScriptRuleHelper(node) {
 function getRuleExportsESM(ast, scopeManager) {
   return (
     ast.body
-      .filter((statement) => statement.type === 'ExportDefaultDeclaration')
-      .map((statement) => statement.declaration)
+      .filter((statement) =>
+        [
+          'ExportDefaultDeclaration', // export default rule;
+          'TSExportAssignment', // export = rule;
+        ].includes(statement.type)
+      )
+      .map((statement) => statement.declaration || statement.expression)
       // eslint-disable-next-line unicorn/prefer-object-from-entries
       .reduce((currentExports, node) => {
         if (node.type === 'ObjectExpression') {
diff --git a/package.json b/package.json
index 8fc34db..2349ba1 100644
--- a/package.json
+++ b/package.json
@@ -1,15 +1,17 @@
 {
   "name": "eslint-plugin-eslint-plugin",
-  "version": "4.4.0",
+  "version": "4.4.1",
   "description": "An ESLint plugin for linting ESLint plugins",
   "author": "Teddy Katz",
-  "main": "lib/index.js",
+  "main": "./lib/index.js",
+  "exports": "./lib/index.js",
   "license": "MIT",
   "scripts": {
+    "generate-readme-table": "node build/generate-readme-table.js",
     "lint": "npm-run-all --continue-on-error --aggregate-output --parallel lint:*",
     "lint:docs": "markdownlint '**/*.md'",
     "lint:js": "eslint --cache .",
-    "generate-readme-table": "node build/generate-readme-table.js",
+    "lint:package-json": "npmPkgJsonLint .",
     "release": "release-it",
     "test": "nyc --all --check-coverage --include lib mocha tests --recursive"
   },
@@ -60,6 +62,7 @@
     "lodash": "^4.17.2",
     "markdownlint-cli": "^0.30.0",
     "mocha": "^9.1.4",
+    "npm-package-json-lint": "^6.3.0",
     "npm-run-all": "^4.1.5",
     "nyc": "^15.1.0",
     "prettier": "^2.5.1",


Test patch:
diff --git a/tests/lib/utils.js b/tests/lib/utils.js
index c6ecb38..66e1b02 100644
--- a/tests/lib/utils.js
+++ b/tests/lib/utils.js
@@ -119,6 +119,9 @@ describe('utils', () => {
         'const notRule = foo(); export default notRule;',
         'const notRule = function(){}; export default notRule;',
         'const notRule = {}; export default notRule;',
+        'const notRule = {}; export = notRule;',
+        'const notRule: Rule.RuleModule = {}; export = notRule;',
+        'export = {};',
       ].forEach((noRuleCase) => {
         it(`returns null for ${noRuleCase}`, () => {
           const ast = typescriptEslintParser.parse(noRuleCase, {
@@ -188,6 +191,65 @@ describe('utils', () => {
             isNewStyle: true,
           },
 
+        // No helper.
+        'export default { create() {}, meta: {} };': {
+          create: { type: 'FunctionExpression' },
+          meta: { type: 'ObjectExpression' },
+          isNewStyle: true,
+        },
+        // No helper, `export =` syntax.
+        'export = { create() {}, meta: {} };': {
+          create: { type: 'FunctionExpression' },
+          meta: { type: 'ObjectExpression' },
+          isNewStyle: true,
+        },
+        // No helper, variable.
+        'const rule = { create() {}, meta: {} }; export default rule;': {
+          create: { type: 'FunctionExpression' },
+          meta: { type: 'ObjectExpression' },
+          isNewStyle: true,
+        },
+        // no helper, variable with type.
+        'const rule: Rule.RuleModule = { create() {}, meta: {} }; export default rule;':
+          {
+            create: { type: 'FunctionExpression' },
+            meta: { type: 'ObjectExpression' },
+            isNewStyle: true,
+          },
+        // No helper, variable, `export =` syntax.
+        'const rule = { create() {}, meta: {} }; export = rule;': {
+          create: { type: 'FunctionExpression' },
+          meta: { type: 'ObjectExpression' },
+          isNewStyle: true,
+        },
+        // No helper, variable with type, `export =` syntax.
+        'const rule: Rule.RuleModule = { create() {}, meta: {} }; export = rule;':
+          {
+            create: { type: 'FunctionExpression' },
+            meta: { type: 'ObjectExpression' },
+            isNewStyle: true,
+          },
+        // Helper, variable, `export =` syntax.
+        'const rule = createESLintRule({ create() {}, meta: {} }); export = rule;':
+          {
+            create: { type: 'FunctionExpression' },
+            meta: { type: 'ObjectExpression' },
+            isNewStyle: true,
+          },
+        // Helper, variable with type, `export =` syntax.
+        'const rule: Rule.RuleModule = createESLintRule({ create() {}, meta: {} }); export = rule;':
+          {
+            create: { type: 'FunctionExpression' },
+            meta: { type: 'ObjectExpression' },
+            isNewStyle: true,
+          },
+        // Helper, `export =` syntax.
+        'export = createESLintRule({ create() {}, meta: {} });': {
+          create: { type: 'FunctionExpression' },
+          meta: { type: 'ObjectExpression' },
+          isNewStyle: true,
+        },
+
         // Util function with "{} as const".
         'export default createESLintRule({ create() {}, meta: {} as const });':
           {