Repository: not-an-aardvark/eslint-plugin-eslint-plugin
Pull request #259 (2022-07-07 17:00:59)

breaking: strictly define Node API
* Add `"exports": "./lib/index.js"` to package.json to strictly define NodeJS API (ESLint itself made the [same change](https://eslint.org/docs/8.0.0/user-guide/migrating-to-8.0.0#the-lib-entrypoint-has-been-removed))

Part of v5 release (https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/issues/230).



Patch:
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 35da37d..232d15d 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -33,7 +33,7 @@ jobs:
       - uses: actions/checkout@v3
       - uses: actions/setup-node@v3
         with:
-          node-version: "16.x"
+          node-version: "18.x"
       - run: npm install
       - run: npm run lint
 
@@ -43,18 +43,18 @@ jobs:
     - uses: actions/checkout@v3
     - uses: actions/setup-node@v3
       with:
-        node-version: "16.x"
+        node-version: "18.x"
     - run: npm install
     - run: npm install --save-dev eslint@6
     - run: npm test
-      
+
   eslint7:
     runs-on: ubuntu-latest
     steps:
     - uses: actions/checkout@v3
     - uses: actions/setup-node@v3
       with:
-        node-version: "16.x"
+        node-version: "18.x"
     - run: npm install
     - run: npm install --save-dev eslint@7
     - run: npm test
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 cbb6bd9..f9c2cd5 100644
--- a/README.md
+++ b/README.md
@@ -74,7 +74,7 @@ Name | ✔️ | 🛠 | 💡 | Description
 [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-object-rule](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/prefer-object-rule.md) | ✔️ | 🛠 |  | disallow rule exports where the export is a function
+[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-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()`
diff --git a/docs/rules/consistent-output.md b/docs/rules/consistent-output.md
index a6d8858..38fb712 100644
--- a/docs/rules/consistent-output.md
+++ b/docs/rules/consistent-output.md
@@ -2,11 +2,11 @@
 
 ✔️ The `"extends": "plugin:eslint-plugin/recommended"` property in a configuration file enables this rule.
 
-When writing tests for fixable rules, it's a best practice to use the `output` property on each test case to assert what autofixed code is produced, or to assert that no autofix is produced using `output: null`.
+When writing tests for fixable rules, the `output` property on each test case can be used to assert what autofixed code is produced, or to assert that no autofix is produced using `output: null`.
 
 Prior to ESLint 7, it was easy to forget to assert the autofix output of a particular test case, resulting in incomplete test coverage and a greater chance of unexpected behavior / bugs.
 
-[As of ESLint 7](https://eslint.org/docs/user-guide/migrating-to-7.0.0#additional-validation-added-to-the-ruletester-class), test cases that trigger an autofix are required to provide the `output` property.
+[As of ESLint 7](https://eslint.org/docs/user-guide/migrating-to-7.0.0#additional-validation-added-to-the-ruletester-class), test cases that trigger an autofix are required by ESLint to provide the `output` property. Thus, it's now acceptable and more concise to omit this property when there's no autofix.
 
 ## Rule Details
 
@@ -71,6 +71,8 @@ This rule takes an optional string enum option with one of the following values:
 
 If you're not writing fixable rules, or you want to write test cases without output assertions, do not enable this rule.
 
+As mentioned in the introduction, the need for this rule is reduced as of ESLint v7.
+
 ## Further Reading
 
 * [`RuleTester` documentation](http://eslint.org/docs/developer-guide/working-with-plugins#testing)
diff --git a/docs/rules/prefer-object-rule.md b/docs/rules/prefer-object-rule.md
index cd853ee..2582d61 100644
--- a/docs/rules/prefer-object-rule.md
+++ b/docs/rules/prefer-object-rule.md
@@ -1,26 +1,22 @@
-# Disallow rule exports where the export is a function (prefer-object-rule)
+# Disallow function-style rules (prefer-object-rule)
 
 ✔️ 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.
 
+Prior to ESLint v9, ESLint supported both [function-style](https://eslint.org/docs/developer-guide/working-with-rules-deprecated) and [object-style](https://eslint.org/docs/developer-guide/working-with-rules) rules. However, function-style rules have been deprecated since 2016, and do not support newer features like autofixing and suggestions.
+
+As of [ESLint v9](https://github.com/eslint/rfcs/tree/main/designs/2021-schema-object-rules#motivation-for-requiring-object-style-rules), ESLint supports only object-style rules.
+
 ## Rule Details
 
-The rule reports an error if it encounters a rule that's defined using the [deprecated style](https://eslint.org/docs/developer-guide/working-with-rules-deprecated) of just a `create` function instead of the newer [object style](https://eslint.org/docs/developer-guide/working-with-rules).
+The rule reports an error if it encounters a rule that's defined using the deprecated function-style format.
 
 Examples of **incorrect** code for this rule:
 
 ```js
 /* eslint eslint-plugin/prefer-object-rule: error */
 
-module.exports = function (context) {
-  return {
-    Program() {
-      context.report();
-    },
-  };
-};
-
 module.exports = function create(context) {
   return {
     Program() {
@@ -28,14 +24,6 @@ module.exports = function create(context) {
     },
   };
 };
-
-module.exports = (context) => {
-  return {
-    Program() {
-      context.report();
-    },
-  };
-};
 ```
 
 Examples of **correct** code for this rule:
@@ -44,6 +32,7 @@ Examples of **correct** code for this rule:
 /* eslint eslint-plugin/prefer-object-rule: error */
 
 module.exports = {
+  meta: { /* ... */ },
   create(context) {
     return {
       Program() {
@@ -52,24 +41,4 @@ module.exports = {
     };
   },
 };
-
-module.exports = {
-  create(context) {
-    return {
-      Program() {
-        context.report();
-      },
-    };
-  },
-};
-
-module.exports = {
-  create: (context) => {
-    return {
-      Program() {
-        context.report();
-      },
-    };
-  },
-};
 ```
diff --git a/docs/rules/require-meta-schema.md b/docs/rules/require-meta-schema.md
index ad8b969..c10e2d5 100644
--- a/docs/rules/require-meta-schema.md
+++ b/docs/rules/require-meta-schema.md
@@ -4,7 +4,9 @@
 
 💡 Some problems reported by this rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
 
-Defining a schema for each rule allows eslint to validate that configuration options are passed correctly. Even when there are no options for a rule, a schema should still be defined (as an empty array) so that eslint can validate that no data is mistakenly passed to the rule.
+Defining a schema for each rule allows eslint to validate that configuration options are passed correctly. Even when there are no options for a rule, a schema can still be defined as an empty array to validate that no data is mistakenly passed to the rule.
+
+As of [ESLint v9](https://github.com/eslint/rfcs/tree/main/designs/2021-schema-object-rules#motivation-for-requiring-schemas), ESLint will validate that options are not provided to a rule when a schema is omitted.
 
 ## Rule Details
 
@@ -75,6 +77,10 @@ This rule takes an optional object containing:
 
 * `boolean` — `requireSchemaPropertyWhenOptionless` — Whether the rule should require the `meta.schema` property to be specified (with `schema: []`) for rules that have no options. Defaults to `true`.
 
+## When Not To Use It
+
+As mentioned in the introduction, the need for this rule is reduced as of ESLint v9.
+
 ## Further Reading
 
 * [working-with-rules#options-schemas](https://eslint.org/docs/developer-guide/working-with-rules#options-schemas)
diff --git a/lib/rules/prefer-object-rule.js b/lib/rules/prefer-object-rule.js
index bb773b0..edcada1 100644
--- a/lib/rules/prefer-object-rule.js
+++ b/lib/rules/prefer-object-rule.js
@@ -15,7 +15,7 @@ module.exports = {
   meta: {
     type: 'suggestion',
     docs: {
-      description: 'disallow rule exports where the export is a function',
+      description: 'disallow function-style rules',
       category: 'Rules',
       recommended: true,
       url: 'https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/tree/HEAD/docs/rules/prefer-object-rule.md',
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/docs/rules/no-identical-tests.md b/docs/rules/no-identical-tests.md
index f74519b..1663e1c 100644
--- a/docs/rules/no-identical-tests.md
+++ b/docs/rules/no-identical-tests.md
@@ -4,18 +4,34 @@
 
 ⚒️ 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.
 
-When a rule has a lot of tests, it's sometimes difficult to tell if any tests are duplicates. This rule would warn if any test cases have the same properties.
+Duplicate test cases can cause confusion, can be hard to detect manually in a long file, and serve no purpose.
+
+As of [ESLint v9](https://github.com/eslint/rfcs/tree/main/designs/2021-stricter-rule-test-validation#disallow-identical-test-cases), ESLint attempts to detect and disallow duplicate tests.
 
 ## Rule Details
 
+This rule detects duplicate test cases.
+
 Examples of **incorrect** code for this rule:
 
 ```js
 /* eslint eslint-plugin/no-identical-tests: error */
 
 new RuleTester().run('foo', bar, {
-  valid: [{ code: 'foo' }, { code: 'foo' }],
-  invalid: [],
+  valid: [
+    'foo',
+    'foo', // duplicate of previous
+  ],
+  invalid: [
+    {
+      code: 'bar',
+      errors: [{ messageId: 'my-message', type: 'CallExpression' }],
+    },
+    {
+      code: 'bar',
+      errors: [{ messageId: 'my-message', type: 'CallExpression' }],
+    }, // duplicate of previous
+  ],
 });
 ```
 
@@ -25,11 +41,12 @@ Examples of **correct** code for this rule:
 /* eslint eslint-plugin/no-identical-tests: error */
 
 new RuleTester().run('foo', bar, {
-  valid: [{ code: 'foo' }, { code: 'bar' }],
-  invalid: [],
+  valid: ['foo', 'bar'],
+  invalid: [
+    {
+      code: 'baz',
+      errors: [{ messageId: 'my-message', type: 'CallExpression' }],
+    },
+  ],
 });
 ```
-
-## When Not To Use It
-
-If you want to allow identical tests, do not enable this rule.
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 });':
           {