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

Feature: importOrderCombineTypeAndValueImports #20

Merged
merged 26 commits into from Oct 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
d4df2f0
Add TypeScript enforcement to not-forgetting our Options schema!
fbartho May 18, 2022
cf4e6af
Change syntax of getCodeFromAst to allow for node deletion
fbartho May 18, 2022
f03f96b
Github-linguist doesn't understand `ecmascript 6`
fbartho May 18, 2022
6c47166
Feature: `importOrderMergeDuplicateImports` - Fixes #4
fbartho May 18, 2022
dc43318
Merge branch 'main' into fb/simplify-import-duplicates
fbartho May 18, 2022
a62bcd1
Feature: `importOrderMergeTypeImportsIntoRegular`
fbartho May 18, 2022
db923e7
merge-tests: extract defaultOptions
fbartho May 18, 2022
e2ab51d
merge-imports: Add test cases from codemod
fbartho May 18, 2022
9ef2abe
switch from toMatchInlineSnapshot to toEqual
fbartho May 19, 2022
61f7797
Merge branch 'fb/simplify-import-duplicates' into fb/merge-type-impor…
fbartho May 19, 2022
8edeb51
switch from toMatchInlineSnapshot to toEqual
fbartho May 19, 2022
f5f9e26
PR Feedback: rename `regular` imports to `value` imports
fbartho May 20, 2022
42999c9
PR Feedback: Readme wording
fbartho May 20, 2022
2fe3128
PR Feedback: rename getCodeFromAst parameters
fbartho May 20, 2022
e393e5e
PR Feedback: import-flavor @returns documentation
fbartho May 20, 2022
e569488
PR Feedback: hoist hasIgnoreNextNode for reuse
fbartho May 20, 2022
0e3a871
PR Feedback: Add strong types for ChunkType/FlavorType
fbartho May 20, 2022
d0a2692
PR Feedback: deleteContext -> nodesToDelete
fbartho May 20, 2022
6cb7764
PR Feedback: comment-update: once you mutate nodes, lines/locations a…
fbartho May 20, 2022
3c3c142
Merge branch 'fb/simplify-import-duplicates' into fb/merge-type-impor…
fbartho May 20, 2022
99b6716
PR Feedback: apply getCodeFromAst renames
fbartho May 20, 2022
826a973
Merge branch 'main' into fb/merge-type-imports-with-regular-imports
fbartho Jun 15, 2022
03b9926
Minor review comments
IanVS Oct 24, 2022
beb9066
Group type imports after value in importOrderSortSpecifiers
IanVS Oct 24, 2022
75b11fe
Rename option for clarity
IanVS Oct 24, 2022
a5e4f6b
Add TOC to readme, re-arrange a bit
IanVS Oct 24, 2022
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
147 changes: 97 additions & 50 deletions README.md
@@ -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
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
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
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
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
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
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',
]);
});