Skip to content

Commit

Permalink
feat: add no-export-default-arrow rule
Browse files Browse the repository at this point in the history
  • Loading branch information
u3u committed Aug 9, 2023
1 parent c1a5a70 commit d17ceb8
Show file tree
Hide file tree
Showing 8 changed files with 218 additions and 6 deletions.
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<br/>_**Automatically fix using the current file name.**_ || 🔧 |

<!-- end auto-generated rules list -->
<!-- prettier-ignore-end -->
Expand Down
23 changes: 23 additions & 0 deletions docs/rules/no-export-default-arrow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Disallow export default anonymous arrow function<br/>_**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).

<!-- end auto-generated rule header -->

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;
```
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
7 changes: 7 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/configs/recommended.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
},
});
6 changes: 4 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -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({
Expand All @@ -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,
},
});
113 changes: 113 additions & 0 deletions src/rules/no-export-default-arrow.test.ts
Original file line number Diff line number Diff line change
@@ -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 = () => {}',
],
});
64 changes: 64 additions & 0 deletions src/rules/no-export-default-arrow.ts
Original file line number Diff line number Diff line change
@@ -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<br/>_**Automatically fix using the current file name.**_',
},

fixable: 'code',

messages: {
disallowExportDefaultArrow: 'Disallow export default anonymous arrow function',
},

schema: [],

type: 'suggestion',
},

name: RULE_NAME,
});

0 comments on commit d17ceb8

Please sign in to comment.