Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[New] prefer-default-export: add "target" option #2602

Merged
merged 1 commit into from Dec 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -17,6 +17,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
- [`order`]: new `alphabetize.orderImportKind` option to sort imports with same path based on their kind (`type`, `typeof`) ([#2544], thanks [@stropho])
- [`consistent-type-specifier-style`]: add rule ([#2473], thanks [@bradzacher])
- Add [`no-empty-named-blocks`] rule ([#2568], thanks [@guilhermelimak])
- [`prefer-default-export`]: add "target" option ([#2602], thanks [@azyzz228])

### Fixed
- [`order`]: move nested imports closer to main import entry ([#2396], thanks [@pri1311])
Expand Down Expand Up @@ -1025,6 +1026,7 @@ for info on changes for earlier releases.
[`memo-parser`]: ./memo-parser/README.md

[#2605]: https://github.com/import-js/eslint-plugin-import/pull/2605
[#2602]: https://github.com/import-js/eslint-plugin-import/pull/2602
[#2598]: https://github.com/import-js/eslint-plugin-import/pull/2598
[#2589]: https://github.com/import-js/eslint-plugin-import/pull/2589
[#2588]: https://github.com/import-js/eslint-plugin-import/pull/2588
Expand Down
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -89,7 +89,7 @@ This plugin intends to support linting of ES2015+ (ES6+) import/export syntax, a
| [no-namespace](docs/rules/no-namespace.md) | Forbid namespace (a.k.a. "wildcard" `*`) imports. | | | | 🔧 | | |
| [no-unassigned-import](docs/rules/no-unassigned-import.md) | Forbid unassigned imports | | | | | | |
| [order](docs/rules/order.md) | Enforce a convention in module import order. | | | | 🔧 | | |
| [prefer-default-export](docs/rules/prefer-default-export.md) | Prefer a default export if module exports a single name. | | | | | | |
| [prefer-default-export](docs/rules/prefer-default-export.md) | Prefer a default export if module exports a single name or multiple names. | | | | | | |

<!-- end auto-generated rules list -->

Expand Down
128 changes: 127 additions & 1 deletion docs/rules/prefer-default-export.md
Expand Up @@ -2,10 +2,44 @@

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

When there is only a single export from a module, prefer using default export over named export.
In exporting files, this rule checks if there is default export or not.

## Rule Details

##### rule schema:

```javascript
"import/prefer-default-export": [
( "off" | "warn" | "error" ),
{ "target": "single" | "any" } // default is "single"
]
```

### Config Options

There are two options available: `single` and `any`. By default, if you do not specify the option, rule will assume it is `single`.

#### single

**Definition**: When there is only a single export from a module, prefer using default export over named export.

How to setup config file for this rule:

```javascript
// you can manually specify it
"rules": {
"import/prefer-default-export": [
( "off" | "warn" | "error" ),
{ "target": "single" }
]
}

// config setup below will also work
"rules": {
"import/prefer-default-export": "off" | "warn" | "error"
}
```

The following patterns are considered warnings:

```javascript
Expand Down Expand Up @@ -58,3 +92,95 @@ export { foo as default }
// Any batch export will disable this rule. The remote module is not inspected.
export * from './other-module'
```

#### any

**Definition**: any exporting file must contain a default export.

How to setup config file for this rule:

```javascript
// you have to manually specify it
"rules": {
"import/prefer-default-export": [
( "off" | "warn" | "error" ),
{ "target": "any" }
]
}
```


The following patterns are *not* considered warnings:

```javascript
// good1.js

//has default export
export default function bar() {};
```

```javascript
// good2.js

// has default export
let foo;
export { foo as default }
```

```javascript
// good3.js

//contains multiple exports AND default export
export const a = 5;
export function bar(){};
let foo;
export { foo as default }
```

```javascript
// good4.js

// does not contain any exports => file is not checked by the rule
import * as foo from './foo';
```

```javascript
// export-star.js

// Any batch export will disable this rule. The remote module is not inspected.
export * from './other-module'
```

The following patterns are considered warnings:

```javascript
// bad1.js

//has 2 named exports, but no default export
export const foo = 'foo';
export const bar = 'bar';
```

```javascript
// bad2.js

// does not have default export
let foo, bar;
export { foo, bar }
```

```javascript
// bad3.js

// does not have default export
export { a, b } from "foo.js"
```

```javascript
// bad4.js

// does not have default export
let item;
export const foo = item;
export { item };
```
29 changes: 24 additions & 5 deletions src/rules/prefer-default-export.js
Expand Up @@ -2,15 +2,28 @@

import docsUrl from '../docsUrl';

const SINGLE_EXPORT_ERROR_MESSAGE = 'Prefer default export on a file with single export.';
const ANY_EXPORT_ERROR_MESSAGE = 'Prefer default export to be present on every file that has export.';

module.exports = {
meta: {
type: 'suggestion',
docs: {
category: 'Style guide',
description: 'Prefer a default export if module exports a single name.',
description: 'Prefer a default export if module exports a single name or multiple names.',
url: docsUrl('prefer-default-export'),
},
schema: [],
schema: [{
type: 'object',
properties:{
target: {
type: 'string',
enum: ['single', 'any'],
azyzz228 marked this conversation as resolved.
Show resolved Hide resolved
default: 'single',
},
},
additionalProperties: false,
ljharb marked this conversation as resolved.
Show resolved Hide resolved
}],
},

create(context) {
Expand All @@ -19,7 +32,8 @@ module.exports = {
let hasStarExport = false;
let hasTypeExport = false;
let namedExportNode = null;

// get options. by default we look into files with single export
const { target = 'single' } = context.options[0] || {};
function captureDeclaration(identifierOrPattern) {
if (identifierOrPattern && identifierOrPattern.type === 'ObjectPattern') {
// recursively capture
Expand Down Expand Up @@ -88,8 +102,13 @@ module.exports = {
},

'Program:exit': function () {
if (specifierExportCount === 1 && !hasDefaultExport && !hasStarExport && !hasTypeExport) {
context.report(namedExportNode, 'Prefer default export.');
if (hasDefaultExport || hasStarExport || hasTypeExport) {
return;
azyzz228 marked this conversation as resolved.
Show resolved Hide resolved
}
if (target === 'single' && specifierExportCount === 1) {
context.report(namedExportNode, SINGLE_EXPORT_ERROR_MESSAGE);
} else if (target === 'any' && specifierExportCount > 0) {
context.report(namedExportNode, ANY_EXPORT_ERROR_MESSAGE);
}
},
};
Expand Down