Skip to content

Commit 8072158

Browse files
vladislavarsenevVladislav Arsenev
andauthored
feature: order respects side effect imports (#320)
Co-authored-by: Vladislav Arsenev <[email protected]>
1 parent e427746 commit 8072158

21 files changed

+854
-105
lines changed

README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,37 @@ with options as a JSON string of the plugin array:
198198
importOrderParserPlugins: []
199199
```
200200

201+
### `importOrderSideEffects`
202+
**type**: `boolean`
203+
**default value**: `true`
204+
205+
By default, the plugin sorts [side effect imports](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#import_a_module_for_its_side_effects_only) like any other imports in the file. If you need to keep side effect imports in the same place but sort all other imports around them, set this option to false.
206+
207+
Example:
208+
209+
Initial file:
210+
211+
```js
212+
import z from 'z'
213+
import a from 'a'
214+
215+
import 'side-effect-lib'
216+
217+
import c from 'c'
218+
import b from 'b'
219+
```
220+
When sorted:
221+
222+
```js
223+
import a from 'a'
224+
import z from 'z'
225+
226+
import 'side-effect-lib'
227+
228+
import b from 'b'
229+
import c from 'c'
230+
```
231+
201232
### How does import sort work ?
202233

203234
The plugin extracts the imports which are defined in `importOrder`. These imports are considered as _local imports_.

examples/example.jsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import otherthing from '@core/otherthing';
88
import twoLevelRelativePath from '../../twoLevelRelativePath';
99
import component from '@ui/hello';
1010

11-
1211
const HelloWorld = ({ name }) => {
1312
return <div>Hello, {name}</div>;
1413
};

src/constants.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ export const newLineCharacters = '\n\n';
99

1010
export const sortImportsIgnoredComment = 'sort-imports-ignore';
1111

12+
export const chunkSideEffectNode = 'side-effect-node';
13+
export const chunkSideOtherNode = 'other-node';
14+
1215
/*
1316
* Used to mark the position between RegExps,
1417
* where the not matched imports should be placed

src/index.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ import { parsers as babelParsers } from 'prettier/plugins/babel';
22
import { parsers as flowParsers } from 'prettier/plugins/flow';
33
import { parsers as htmlParsers } from 'prettier/plugins/html';
44
import { parsers as typescriptParsers } from 'prettier/plugins/typescript';
5+
56
import { defaultPreprocessor } from './preprocessors/default-processor';
6-
import { vuePreprocessor } from './preprocessors/vue-preprocessor';
77
import { sveltePreprocessor } from './preprocessors/svelte-preprocessor';
8+
import { vuePreprocessor } from './preprocessors/vue-preprocessor';
89

910
const { parsers: svelteParsers } = require('prettier-plugin-svelte');
1011

@@ -50,6 +51,12 @@ const options = {
5051
default: false,
5152
description: 'Should specifiers be sorted?',
5253
},
54+
importOrderSideEffects: {
55+
type: 'boolean',
56+
category: 'Global',
57+
default: true,
58+
description: 'Should side effects be sorted?',
59+
},
5360
};
5461

5562
module.exports = {

src/preprocessors/preprocessor.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export function preprocessor(code: string, options: PrettierOptions) {
1616
importOrderSeparation,
1717
importOrderGroupNamespaceSpecifiers,
1818
importOrderSortSpecifiers,
19+
importOrderSideEffects,
1920
} = options;
2021

2122
const parserOptions: ParserOptions = {
@@ -42,6 +43,7 @@ export function preprocessor(code: string, options: PrettierOptions) {
4243
importOrderSeparation,
4344
importOrderGroupNamespaceSpecifiers,
4445
importOrderSortSpecifiers,
46+
importOrderSideEffects,
4547
});
4648

4749
return getCodeFromAst(allImports, directives, code, interpreter);

src/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,11 @@ export type GetSortedNodes = (
1919
| 'importOrderSeparation'
2020
| 'importOrderGroupNamespaceSpecifiers'
2121
| 'importOrderSortSpecifiers'
22+
| 'importOrderSideEffects'
2223
>,
2324
) => ImportOrLine[];
25+
26+
export interface ImportChunk {
27+
nodes: ImportDeclaration[];
28+
type: string;
29+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { ImportDeclaration } from '@babel/types';
2+
3+
import { adjustCommentsOnSortedNodes } from '../adjust-comments-on-sorted-nodes';
4+
import { getImportNodes } from '../get-import-nodes';
5+
6+
function leadingComments(node: ImportDeclaration): string[] {
7+
return node.leadingComments?.map((c) => c.value) ?? [];
8+
}
9+
10+
function trailingComments(node: ImportDeclaration): string[] {
11+
return node.trailingComments?.map((c) => c.value) ?? [];
12+
}
13+
14+
test('it preserves the single leading comment for each import declaration', () => {
15+
const importNodes = getImportNodes(`
16+
import {x} from "c";
17+
// comment b
18+
import {y} from "b";
19+
// comment a
20+
import {z} from "a";
21+
`);
22+
expect(importNodes).toHaveLength(3);
23+
const finalNodes = [importNodes[2], importNodes[1], importNodes[0]];
24+
adjustCommentsOnSortedNodes(importNodes, finalNodes);
25+
expect(finalNodes).toHaveLength(3);
26+
expect(leadingComments(finalNodes[0])).toEqual([' comment a']);
27+
expect(trailingComments(finalNodes[0])).toEqual([]);
28+
expect(leadingComments(finalNodes[1])).toEqual([' comment b']);
29+
expect(trailingComments(finalNodes[1])).toEqual([]);
30+
expect(leadingComments(finalNodes[2])).toEqual([]);
31+
expect(trailingComments(finalNodes[2])).toEqual([]);
32+
});
33+
34+
test('it preserves multiple leading comments for each import declaration', () => {
35+
const importNodes = getImportNodes(`
36+
import {x} from "c";
37+
// comment b1
38+
// comment b2
39+
// comment b3
40+
import {y} from "b";
41+
// comment a1
42+
// comment a2
43+
// comment a3
44+
import {z} from "a";
45+
`);
46+
expect(importNodes).toHaveLength(3);
47+
const finalNodes = [importNodes[2], importNodes[1], importNodes[0]];
48+
adjustCommentsOnSortedNodes(importNodes, finalNodes);
49+
expect(finalNodes).toHaveLength(3);
50+
expect(leadingComments(finalNodes[0])).toEqual([
51+
' comment a1',
52+
' comment a2',
53+
' comment a3',
54+
]);
55+
expect(trailingComments(finalNodes[0])).toEqual([]);
56+
expect(leadingComments(finalNodes[1])).toEqual([
57+
' comment b1',
58+
' comment b2',
59+
' comment b3',
60+
]);
61+
expect(trailingComments(finalNodes[1])).toEqual([]);
62+
expect(leadingComments(finalNodes[2])).toEqual([]);
63+
expect(trailingComments(finalNodes[2])).toEqual([]);
64+
});
65+
66+
test('it does not move comments at before all import declarations', () => {
67+
const importNodes = getImportNodes(`
68+
// comment c1
69+
// comment c2
70+
import {x} from "c";
71+
import {y} from "b";
72+
import {z} from "a";
73+
`);
74+
expect(importNodes).toHaveLength(3);
75+
const finalNodes = [importNodes[2], importNodes[1], importNodes[0]];
76+
adjustCommentsOnSortedNodes(importNodes, finalNodes);
77+
expect(finalNodes).toHaveLength(3);
78+
expect(leadingComments(finalNodes[0])).toEqual([
79+
' comment c1',
80+
' comment c2',
81+
]);
82+
expect(trailingComments(finalNodes[0])).toEqual([]);
83+
expect(leadingComments(finalNodes[1])).toEqual([]);
84+
expect(trailingComments(finalNodes[1])).toEqual([]);
85+
expect(leadingComments(finalNodes[2])).toEqual([]);
86+
expect(trailingComments(finalNodes[2])).toEqual([]);
87+
});
88+
89+
test('it does not affect comments after all import declarations', () => {
90+
const importNodes = getImportNodes(`
91+
import {x} from "c";
92+
import {y} from "b";
93+
import {z} from "a";
94+
// comment final 1
95+
// comment final 2
96+
`);
97+
expect(importNodes).toHaveLength(3);
98+
const finalNodes = [importNodes[2], importNodes[1], importNodes[0]];
99+
adjustCommentsOnSortedNodes(importNodes, finalNodes);
100+
expect(finalNodes).toHaveLength(3);
101+
expect(leadingComments(finalNodes[0])).toEqual([]);
102+
expect(trailingComments(finalNodes[0])).toEqual([]);
103+
expect(leadingComments(finalNodes[1])).toEqual([]);
104+
expect(trailingComments(finalNodes[1])).toEqual([]);
105+
expect(leadingComments(finalNodes[2])).toEqual([]);
106+
expect(trailingComments(finalNodes[2])).toEqual([]);
107+
});

src/utils/__tests__/get-all-comments-from-nodes.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const getSortedImportNodes = (code: string, options?: ParserOptions) => {
1414
importOrderSeparation: false,
1515
importOrderGroupNamespaceSpecifiers: false,
1616
importOrderSortSpecifiers: false,
17+
importOrderSideEffects: true,
1718
});
1819
};
1920

src/utils/__tests__/get-code-from-ast.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import a from 'a';
2525
importOrderSeparation: false,
2626
importOrderGroupNamespaceSpecifiers: false,
2727
importOrderSortSpecifiers: false,
28+
importOrderSideEffects: true,
2829
});
2930
const formatted = getCodeFromAst(sortedNodes, [], code, null);
3031
expect(await format(formatted, { parser: 'babel' })).toEqual(

0 commit comments

Comments
 (0)