diff --git a/README.md b/README.md
index afa74d7..6cce5dc 100644
--- a/README.md
+++ b/README.md
@@ -53,9 +53,10 @@ If set `true`, always use explicit return.
✅ Set in the `recommended` configuration.\
🔧 Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).
-| Name | Description | ⚠️ | 🔧 |
-| :----------------------------------------------------- | :---------------------------------- | :- | :- |
-| [arrow-return-style](docs/rules/arrow-return-style.md) | Enforce arrow function return style | ✅ | 🔧 |
+| Name | Description | ⚠️ | 🔧 |
+| :--------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------- | :- | :- |
+| [arrow-return-style](docs/rules/arrow-return-style.md) | Enforce arrow function return style | ✅ | 🔧 |
+| [no-export-default-arrow](docs/rules/no-export-default-arrow.md) | Disallow export default anonymous arrow function
_**Automatically fix using the current file name.**_ | ✅ | 🔧 |
diff --git a/docs/rules/no-export-default-arrow.md b/docs/rules/no-export-default-arrow.md
new file mode 100644
index 0000000..fcc8ec9
--- /dev/null
+++ b/docs/rules/no-export-default-arrow.md
@@ -0,0 +1,23 @@
+# Disallow export default anonymous arrow function
_**Automatically fix using the current file name.**_ (`arrow-return-style/no-export-default-arrow`)
+
+⚠️ This rule _warns_ in the ✅ `recommended` config.
+
+🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
+
+
+
+If the default export is an anonymous arrow function, it will automatically be converted to a named function using the current file name for exporting.
+
+```ts
+export default () => {
+ // ...
+};
+
+// ↓↓↓↓↓↓↓↓↓↓
+
+const autoFixToCurrentFileName = () => {
+ // ...
+};
+
+export default autoFixToCurrentFileName;
+```
diff --git a/package.json b/package.json
index b84cf60..0619b34 100644
--- a/package.json
+++ b/package.json
@@ -54,7 +54,8 @@
]
},
"dependencies": {
- "@typescript-eslint/utils": "^6.3.0"
+ "@typescript-eslint/utils": "^6.3.0",
+ "scule": "^1.0.0"
},
"devDependencies": {
"@tsconfig/node18": "^18.2.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 4402a32..b3ad47a 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -8,6 +8,9 @@ dependencies:
'@typescript-eslint/utils':
specifier: ^6.3.0
version: 6.3.0(eslint@8.46.0)(typescript@5.1.6)
+ scule:
+ specifier: ^1.0.0
+ version: 1.0.0
devDependencies:
'@tsconfig/node18':
@@ -4989,6 +4992,10 @@ packages:
regexp-ast-analysis: 0.6.0
dev: true
+ /scule@1.0.0:
+ resolution: {integrity: sha512-4AsO/FrViE/iDNEPaAQlb77tf0csuq27EsVpy6ett584EcRTp6pTDLoGWVxCD77y5iU5FauOvhsI4o1APwPoSQ==}
+ dev: false
+
/semver@5.7.1:
resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==}
hasBin: true
diff --git a/src/configs/recommended.ts b/src/configs/recommended.ts
index 8564e97..0bf0e64 100644
--- a/src/configs/recommended.ts
+++ b/src/configs/recommended.ts
@@ -6,5 +6,6 @@ export default defineConfig({
rules: {
'arrow-body-style': 'off',
'arrow-return-style/arrow-return-style': 'warn',
+ 'arrow-return-style/no-export-default-arrow': 'warn',
},
});
diff --git a/src/index.ts b/src/index.ts
index e5e9c48..6e2bd48 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,7 +1,8 @@
import type { Rule } from 'eslint';
import { name, version } from '../package.json';
import recommended from './configs/recommended';
-import { arrowReturnStyleRule, RULE_NAME } from './rules/arrow-return-style';
+import { arrowReturnStyleRule, RULE_NAME as arrowReturnStyleRuleName } from './rules/arrow-return-style';
+import { noExportDefaultArrowRule, RULE_NAME as noExportDefaultArrowRuleName } from './rules/no-export-default-arrow';
import { definePlugin } from './utils';
export default definePlugin({
@@ -15,6 +16,7 @@ export default definePlugin({
},
rules: {
- [RULE_NAME]: arrowReturnStyleRule as unknown as Rule.RuleModule,
+ [arrowReturnStyleRuleName]: arrowReturnStyleRule as unknown as Rule.RuleModule,
+ [noExportDefaultArrowRuleName]: noExportDefaultArrowRule as unknown as Rule.RuleModule,
},
});
diff --git a/src/rules/no-export-default-arrow.test.ts b/src/rules/no-export-default-arrow.test.ts
new file mode 100644
index 0000000..5356a1c
--- /dev/null
+++ b/src/rules/no-export-default-arrow.test.ts
@@ -0,0 +1,113 @@
+import { RuleTester } from '@typescript-eslint/rule-tester';
+import dedent from 'dedent';
+import { afterAll, describe, it } from 'vitest';
+import { noExportDefaultArrowRule, RULE_NAME } from './no-export-default-arrow';
+
+RuleTester.afterAll = afterAll;
+RuleTester.describe = describe;
+RuleTester.it = it;
+
+const ruleTester = new RuleTester({
+ parser: '@typescript-eslint/parser',
+
+ parserOptions: {
+ ecmaFeatures: { jsx: true },
+ },
+});
+
+ruleTester.run(RULE_NAME, noExportDefaultArrowRule, {
+ invalid: [
+ {
+ code: dedent`
+ import { useState } from 'react'
+
+ export default () => {
+ const [, update] = useState({})
+
+ const forceUpdate = () => {
+ update({})
+ }
+
+ return forceUpdate
+ }
+ `,
+
+ errors: [{ messageId: 'disallowExportDefaultArrow' }],
+
+ filename: 'useForceUpdate.ts',
+
+ output: dedent`
+ import { useState } from 'react'
+
+ const useForceUpdate = () => {
+ const [, update] = useState({})
+
+ const forceUpdate = () => {
+ update({})
+ }
+
+ return forceUpdate
+ }
+
+ export default useForceUpdate
+ `,
+ },
+
+ {
+ code: dedent`
+ export default () => {}
+
+ export const foo = () => 'foo'
+ `,
+
+ errors: [{ messageId: 'disallowExportDefaultArrow' }],
+
+ filename: 'use-mouse.tsx',
+
+ output: dedent`
+ const useMouse = () => {}
+
+ export const foo = () => 'foo'
+
+ export default useMouse
+ `,
+ },
+
+ {
+ code: dedent`
+ export default () => 1
+
+ // line comment
+
+ /* block comment */
+ `,
+
+ errors: [{ messageId: 'disallowExportDefaultArrow' }],
+
+ filename: 'just_for_fun.js',
+
+ output: dedent`
+ const justForFun = () => 1
+
+ // line comment
+
+ /* block comment */
+
+ export default justForFun
+ `,
+ },
+ ],
+
+ valid: [
+ dedent`
+ const foo = () => {
+ return 'foo'
+ }
+
+ export default foo
+ `,
+
+ 'const now = () => Date.now()',
+ 'export const useQuery = () => {}',
+ ],
+});
diff --git a/src/rules/no-export-default-arrow.ts b/src/rules/no-export-default-arrow.ts
new file mode 100644
index 0000000..1a13fa0
--- /dev/null
+++ b/src/rules/no-export-default-arrow.ts
@@ -0,0 +1,64 @@
+import path from 'node:path';
+import { AST_NODE_TYPES, ASTUtils, type TSESTree } from '@typescript-eslint/utils';
+import { camelCase } from 'scule';
+import { createRule } from '../utils/create-rule';
+
+export const RULE_NAME = 'no-export-default-arrow';
+
+export const noExportDefaultArrowRule = createRule({
+ create: (context) => {
+ const sourceCode = context.getSourceCode();
+ let program: TSESTree.Program;
+
+ return {
+ ArrowFunctionExpression: (arrowFunction) => {
+ const { body: arrowBody, parent: arrowFunctionParent } = arrowFunction;
+
+ if (arrowFunctionParent.type === AST_NODE_TYPES.ExportDefaultDeclaration) {
+ context.report({
+ fix: (fixer) => {
+ const fixes = [];
+ const lastToken = sourceCode.getLastToken(program, { includeComments: true }) || arrowFunctionParent;
+ const fileName = context.getPhysicalFilename?.() || context.getFilename() || 'namedFunction';
+ const { name: fileNameWithoutExtension } = path.parse(fileName);
+ const funcName = camelCase(fileNameWithoutExtension);
+
+ fixes.push(
+ fixer.replaceText(arrowFunctionParent, `const ${funcName} = ${sourceCode.getText(arrowFunction)}`),
+ fixer.insertTextAfter(lastToken, `\n\nexport default ${funcName}`)
+ );
+
+ return fixes;
+ },
+
+ messageId: 'disallowExportDefaultArrow',
+ node: arrowFunction,
+ });
+ }
+ },
+
+ Program: (node) => (program = node),
+ };
+ },
+
+ defaultOptions: [],
+
+ meta: {
+ docs: {
+ description:
+ 'Disallow export default anonymous arrow function
_**Automatically fix using the current file name.**_',
+ },
+
+ fixable: 'code',
+
+ messages: {
+ disallowExportDefaultArrow: 'Disallow export default anonymous arrow function',
+ },
+
+ schema: [],
+
+ type: 'suggestion',
+ },
+
+ name: RULE_NAME,
+});