Skip to content

Commit

Permalink
[New] prefer-default-export: add "target" option
Browse files Browse the repository at this point in the history
Fixes #2600.
  • Loading branch information
azyzz228 authored and ljharb committed Nov 24, 2022
1 parent f4f305b commit 922819f
Show file tree
Hide file tree
Showing 5 changed files with 337 additions and 13 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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'],
default: 'single',
},
},
additionalProperties: false,
}],
},

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;
}
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
Loading

0 comments on commit 922819f

Please sign in to comment.