Skip to content

Commit

Permalink
Feature: importOrderMergeTypeImportsIntoRegular (#20)
Browse files Browse the repository at this point in the history
Allows the ability for users to convert import type expressions into import {type …} expressions via a boolean flag.
  • Loading branch information
fbartho authored Oct 24, 2022
1 parent 3233783 commit b7c8ca5
Show file tree
Hide file tree
Showing 14 changed files with 468 additions and 116 deletions.
147 changes: 97 additions & 50 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Prettier plugin sort imports
# Prettier plugin sort imports <!-- omit in toc -->

A prettier plugin to sort import declarations by provided Regular Expression order.

Expand All @@ -10,6 +10,30 @@ Since then more critical features & fixes have been added. As a result, this rep

[We welcome contributions!](./CONTRIBUTING.md)

**Table of Contents**

- [Sample](#sample)
- [Input](#input)
- [Output](#output)
- [Install](#install)
- [Usage](#usage)
- [How does import sort work?](#how-does-import-sort-work)
- [Options](#options)
- [`importOrder`](#importorder)
- [`importOrderSeparation`](#importorderseparation)
- [`importOrderSortSpecifiers`](#importordersortspecifiers)
- [`importOrderGroupNamespaceSpecifiers`](#importordergroupnamespacespecifiers)
- [`importOrderCaseInsensitive`](#importordercaseinsensitive)
- [`importOrderMergeDuplicateImports`](#importordermergeduplicateimports)
- [`importOrderCombineTypeAndValueImports`](#importordercombinetypeandvalueimports)
- [`importOrderParserPlugins`](#importorderparserplugins)
- [`importOrderBuiltinModulesToTop`](#importorderbuiltinmodulestotop)
- [Prevent imports from being sorted](#prevent-imports-from-being-sorted)
- [FAQ / Troubleshooting](#faq--troubleshooting)
- [Compatibility](#compatibility)
- [Contribution](#contribution)
- [Disclaimer](#disclaimer)

## Sample

### Input
Expand Down Expand Up @@ -100,32 +124,59 @@ module.exports = {
"importOrderCaseInsensitive": true,
"importOrderParserPlugins": ["typescript", "jsx", "decorators-legacy"],
"importOrderMergeDuplicateImports": true,
"importOrderCombineTypeAndValueImports": true,
"importOrderSeparation": true,
"importOrderSortSpecifiers": true,
}
```

_Note: all flags are off by default, so explore your options [below](#apis)_
_Note: all flags are off by default, so explore your options [below](#options)_

### APIs
### How does import sort work?

#### Prevent imports from being sorted
The plugin extracts the imports which are defined in `importOrder`. These imports are considered as _local imports_.
The imports which are not part of the `importOrder` is considered as _third party imports_.

This plugin supports standard prettier ignore comments. By default, side-effect imports (like
`import "core-js/stable";`) are not sorted, so in most cases things should just work. But if you ever need to, you can
prevent an import from getting sorted like this:
First, the plugin checks for
[side effect imports](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#import_a_module_for_its_side_effects_only),
such as `import 'mock-fs'`. These imports often modify the global scope or apply some patches to the current
environment, which may affect other imports. To preserve potential side effects, these kind of side effect imports are
classified as unsortable. They also behave as a barrier that other imports may not cross during the sort. So for
example, let's say you've got these imports:

```javascript
// prettier-ignore
import { goods } from "zealand";
import { cars } from "austria";
import E from 'e';
import F from 'f';
import D from 'd';
import 'c';
import B from 'b';
import A from 'a';
```

This will keep the `zealand` import at the top instead of moving it below the `austria` import. Note that since only
entire import statements can be ignored, line comments (`// prettier-ignore`) are recommended over inline comments
(`/* prettier-ignore */`).
Then the first three imports are sorted and the last two imports are sorted, but all imports above `c` stay above `c`
and all imports below `c` stay below `c`, resulting in:

#### **`importOrder`**
```javascript
import D from 'd';
import E from 'e';
import F from 'f';
import 'c';
import A from 'a';
import B from 'b';
```

Additionally, any import statements lines that are preceded by a `// prettier-ignore` comment are also classified as
unsortable. This can be used for edge-cases, such as when you have a named import with side-effects.

Next, the plugin sorts the _local imports_ and _third party imports_ using [natural sort algorithm](https://en.wikipedia.org/wiki/Natural_sort_order).

In the end, the plugin returns final imports with _third party imports_ on top and _local imports_ at the end.

The _third party imports_ position (it's top by default) can be overridden using the `<THIRD_PARTY_MODULES>` special word in the `importOrder`.

### Options

#### `importOrder`

**type**: `Array<string>`

Expand Down Expand Up @@ -218,6 +269,28 @@ import ExampleView from './ExampleView';

When `true`, multiple import statements from the same module will be combined into a single import.

#### `importOrderCombineTypeAndValueImports`

**type**: `boolean`

**default value:** `false`

A boolean value to control merging `import type` expressions into `import {…}`.

```diff
- import type { C1 } from 'c';
- import { C2 } from 'c';
+ import { type C1, C2 } from "c";

- import { D1 } from 'd';
- import type { D2 } from 'd';
+ import { D1, type D2 } from "d";

- import type { A1 } from 'a';
- import type { A2 } from 'a';
+ import type { A1, A2 } from "a";
```

#### `importOrderParserPlugins`

**type**: `Array<string>`
Expand Down Expand Up @@ -258,47 +331,21 @@ with options as a JSON string of the plugin array:

A boolean value to enable sorting of [`node builtins`](https://nodejs.org/api/module.html#modulebuiltinmodules) to the top of all import groups.

### How does import sort work?

The plugin extracts the imports which are defined in `importOrder`. These imports are considered as _local imports_.
The imports which are not part of the `importOrder` is considered as _third party imports_.

First, the plugin checks for
[side effect imports](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#import_a_module_for_its_side_effects_only),
such as `import 'mock-fs'`. These imports often modify the global scope or apply some patches to the current
environment, which may affect other imports. To preserve potential side effects, these kind of side effect imports are
classified as unsortable. They also behave as a barrier that other imports may not cross during the sort. So for
example, let's say you've got these imports:

```javascript
import E from 'e';
import F from 'f';
import D from 'd';
import 'c';
import B from 'b';
import A from 'a';
```
### Prevent imports from being sorted

Then the first three imports are sorted and the last two imports are sorted, but all imports above `c` stay above `c`
and all imports below `c` stay below `c`, resulting in:
This plugin supports standard prettier ignore comments. By default, side-effect imports (like
`import "core-js/stable";`) are not sorted, so in most cases things should just work. But if you ever need to, you can
prevent an import from getting sorted like this:

```javascript
import D from 'd';
import E from 'e';
import F from 'f';
import 'c';
import A from 'a';
import B from 'b';
// prettier-ignore
import { goods } from "zealand";
import { cars } from "austria";
```

Additionally, any import statements lines that are preceded by a `// prettier-ignore` comment are also classified as
unsortable. This can be used for edge-cases, such as when you have a named import with side-effects.

Next, the plugin sorts the _local imports_ and _third party imports_ using [natural sort algorithm](https://en.wikipedia.org/wiki/Natural_sort_order).

In the end, the plugin returns final imports with _third party imports_ on top and _local imports_ at the end.

The _third party imports_ position (it's top by default) can be overridden using the `<THIRD_PARTY_MODULES>` special word in the `importOrder`.
This will keep the `zealand` import at the top instead of moving it below the `austria` import. Note that since only
entire import statements can be ignored, line comments (`// prettier-ignore`) are recommended over inline comments
(`/* prettier-ignore */`).

## FAQ / Troubleshooting

Expand Down
7 changes: 7 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,13 @@ const options: Record<
default: false,
description: 'Should duplicate imports be merged?',
},
importOrderCombineTypeAndValueImports: {
type: 'boolean',
category: 'Global',
default: false,
description:
'Should import-type expressions be merged into import-value expressions?',
},
};

module.exports = {
Expand Down
11 changes: 11 additions & 0 deletions src/preprocessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,20 @@ export function preprocessor(code: string, options: PrettierOptions): string {
importOrderCaseInsensitive,
importOrderGroupNamespaceSpecifiers,
importOrderMergeDuplicateImports,
importOrderCombineTypeAndValueImports,
importOrderSeparation,
importOrderSortSpecifiers,
} = options;

if (
importOrderCombineTypeAndValueImports &&
!importOrderMergeDuplicateImports
) {
console.warn(
'[@ianvs/prettier-plugin-sort-imports]: Enabling importOrderCombineTypeAndValueImports will have no effect unless importOrderMergeDuplicateImports is also enabled.',
);
}

const allOriginalImportNodes: ImportDeclaration[] = [];
const parserOptions: ParserOptions = {
sourceType: 'module',
Expand Down Expand Up @@ -52,6 +62,7 @@ export function preprocessor(code: string, options: PrettierOptions): string {
importOrderCaseInsensitive,
importOrderGroupNamespaceSpecifiers,
importOrderMergeDuplicateImports,
importOrderCombineTypeAndValueImports,
importOrderSeparation,
importOrderSortSpecifiers,
});
Expand Down
3 changes: 3 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export interface PrettierOptions extends RequiredOptions {
importOrderBuiltinModulesToTop: boolean;
importOrderGroupNamespaceSpecifiers: boolean;
importOrderMergeDuplicateImports: boolean;
importOrderCombineTypeAndValueImports: boolean;
importOrderSeparation: boolean;
importOrderSortSpecifiers: boolean;
// should be of type ParserPlugin from '@babel/parser' but prettier does not support nested arrays in options
Expand Down Expand Up @@ -46,6 +47,7 @@ export type GetSortedNodes = (
| 'importOrderCaseInsensitive'
| 'importOrderGroupNamespaceSpecifiers'
| 'importOrderMergeDuplicateImports'
| 'importOrderCombineTypeAndValueImports'
| 'importOrderSeparation'
| 'importOrderSortSpecifiers'
>,
Expand All @@ -57,4 +59,5 @@ export type GetImportFlavorOfNode = (node: ImportDeclaration) => FlavorType;

export type MergeNodesWithMatchingImportFlavors = (
nodes: ImportDeclaration[],
options: { importOrderCombineTypeAndValueImports: boolean },
) => ImportDeclaration[];
1 change: 1 addition & 0 deletions src/utils/__tests__/get-all-comments-from-nodes.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const getSortedImportNodes = (code: string, options?: ParserOptions) => {
importOrderCaseInsensitive: false,
importOrderGroupNamespaceSpecifiers: false,
importOrderMergeDuplicateImports: false,
importOrderCombineTypeAndValueImports: false,
importOrderSeparation: false,
importOrderSortSpecifiers: false,
});
Expand Down
9 changes: 5 additions & 4 deletions src/utils/__tests__/get-code-from-ast.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import a from 'a';
importOrderCaseInsensitive: false,
importOrderGroupNamespaceSpecifiers: false,
importOrderMergeDuplicateImports: false,
importOrderCombineTypeAndValueImports: false,
importOrderSeparation: false,
importOrderSortSpecifiers: false,
});
Expand All @@ -47,14 +48,13 @@ it('merges duplicate imports correctly', () => {
// second comment
import z from 'z';
import c from 'c';
import type {C} from 'c';
import type {See} from 'c';
import g from 'g';
import t from 't';
import k from 'k';
import a from 'a';
import {b} from 'a';
import {type Bee} from 'a';
import {b, type Bee} from 'a';
import type {C} from 'c';
import type {See} from 'c';
`;
const importNodes = getImportNodes(code, { plugins: ['typescript'] });
const sortedNodes = getSortedNodes(importNodes, {
Expand All @@ -63,6 +63,7 @@ import {type Bee} from 'a';
importOrderCaseInsensitive: false,
importOrderGroupNamespaceSpecifiers: false,
importOrderMergeDuplicateImports: true,
importOrderCombineTypeAndValueImports: false,
importOrderSeparation: false,
importOrderSortSpecifiers: false,
});
Expand Down
20 changes: 20 additions & 0 deletions src/utils/__tests__/get-sorted-import-specifiers.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,23 @@ test('should return correct sorted nodes with default import', () => {
'reduce',
]);
});

test('should group type imports after value imports', () => {
const code = `import Component, { type TypeB, filter, type TypeA, reduce, eventHandler } from '@server/z';`;
const [importNode] = getImportNodes(code, {
plugins: ['typescript'],
});
const sortedImportSpecifiers = getSortedImportSpecifiers(importNode);
const specifiersList = getSortedNodesModulesNames(
sortedImportSpecifiers.specifiers,
);

expect(specifiersList).toEqual([
'Component',
'eventHandler',
'filter',
'reduce',
'TypeA',
'TypeB',
]);
});
Loading

0 comments on commit b7c8ca5

Please sign in to comment.