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

Do not reorder side effect nodes #110 #111

Merged
Merged
30 changes: 29 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,35 @@ importOrderParserPlugins: []
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_.

After, the plugin sorts the _local imports_ and _third party imports_ using [natural sort algorithm](https://en.wikipedia.org/wiki/Natural_sort_order).
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
not sorted. 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';
```

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:

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

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.

Expand Down
4 changes: 2 additions & 2 deletions docs/DEBUG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@

### How to run example in the repository ?
```shell
yarn run example examples/example.ts --config .prettierrc
yarn run example examples/example.ts --config examples/.prettierrc
```

### How to debug the plugin using node `debugger` ?
You can set a `debugger` anywhere in the code and then use following command:
```shell
yarn run compile && node --inspect-brk ./node_modules/.bin/prettier --config .prettierrc --plugin lib/src/index.js examples/example.ts
yarn run compile && node --inspect-brk ./node_modules/.bin/prettier --config examples/.prettierrc --plugin lib/src/index.js examples/example.ts
```

### How to debug the unit test using `debugger` ?
Expand Down
2 changes: 1 addition & 1 deletion examples/example.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import React, {
} from 'react';
import { logger } from '@core/logger';
import { reduce, debounce } from 'lodash';
import { Message } from '../Mesage';
import { Message } from '../Message';
import { createServer } from '@server/node';
import { Alert } from '@ui/Alert';
import { repeat, filter, add } from './utils';
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@
"devDependencies": {
"@types/chai": "4.2.15",
"@types/jest": "26.0.20",
"@types/node": "14.14.34",
"@types/lodash": "4.14.168",
"@types/node": "14.14.34",
"jest": "26.6.3",
"prettier": "2.3.1",
"ts-jest": "26.5.3",
Expand Down
3 changes: 3 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ export const jsx: ParserPlugin = 'jsx';

export const newLineCharacters = '\n\n';

export const ChunkSideEffectNode = 'side-effect-node';
blutorange marked this conversation as resolved.
Show resolved Hide resolved
export const ChunkSideOtherNode = 'other-node';
blutorange marked this conversation as resolved.
Show resolved Hide resolved

/*
* Used to mark the position between RegExps,
* where the not matched imports should be placed
Expand Down
5 changes: 5 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ export interface PrettierOptions extends RequiredOptions {
importOrderSortSpecifiers: boolean;
}

export interface ImportChunk {
nodes: ImportDeclaration[];
type: string;
}

export type ImportGroups = Record<string, ImportDeclaration[]>;
export type ImportOrLine = ImportDeclaration | ExpressionStatement;

Expand Down
285 changes: 285 additions & 0 deletions src/utils/__tests__/get-sorted-nodes-by-import-order.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,285 @@
import { ImportDeclaration } from '@babel/types';

import { getImportNodes } from '../get-import-nodes';
import { getSortedNodesByImportOrder } from '../get-sorted-nodes-by-import-order';
import { getSortedNodesModulesNames } from '../get-sorted-nodes-modules-names';
import { getSortedNodesNames } from '../get-sorted-nodes-names';

const code = `// first comment
// second comment
import z from 'z';
import c, { cD } from 'c';
import g from 'g';
import { tC, tA, tB } from 't';
import k, { kE, kB } from 'k';
import * as a from 'a';
import BY from 'BY';
import Ba from 'Ba';
import XY from 'XY';
import Xa from 'Xa';
`;

test('it returns all sorted nodes', () => {
const result = getImportNodes(code);
const sorted = getSortedNodesByImportOrder(result, {
importOrder: [],
importOrderCaseInsensitive: false,
importOrderSeparation: false,
importOrderSortSpecifiers: false,
}) as ImportDeclaration[];

expect(getSortedNodesNames(sorted)).toEqual([
'BY',
'Ba',
'XY',
'Xa',
'a',
'c',
'g',
'k',
't',
'z',
]);
expect(
sorted
.filter((node) => node.type === 'ImportDeclaration')
.map((importDeclaration) =>
getSortedNodesModulesNames(importDeclaration.specifiers),
),
).toEqual([
['BY'],
['Ba'],
['XY'],
['Xa'],
['a'],
['c', 'cD'],
['g'],
['k', 'kE', 'kB'],
['tC', 'tA', 'tB'],
['z'],
]);
});

test('it returns all sorted nodes case-insensitive', () => {
const result = getImportNodes(code);
const sorted = getSortedNodesByImportOrder(result, {
importOrder: [],
importOrderCaseInsensitive: true,
importOrderSeparation: false,
importOrderSortSpecifiers: false,
}) as ImportDeclaration[];

expect(getSortedNodesNames(sorted)).toEqual([
'a',
'Ba',
'BY',
'c',
'g',
'k',
't',
'Xa',
'XY',
'z',
]);
expect(
sorted
.filter((node) => node.type === 'ImportDeclaration')
.map((importDeclaration) =>
getSortedNodesModulesNames(importDeclaration.specifiers),
),
).toEqual([
['a'],
['Ba'],
['BY'],
['c', 'cD'],
['g'],
['k', 'kE', 'kB'],
['tC', 'tA', 'tB'],
['Xa'],
['XY'],
['z'],
]);
});

test('it returns all sorted nodes with sort order', () => {
const result = getImportNodes(code);
const sorted = getSortedNodesByImportOrder(result, {
importOrder: ['^a$', '^t$', '^k$', '^B'],
importOrderCaseInsensitive: false,
importOrderSeparation: false,
importOrderSortSpecifiers: false,
}) as ImportDeclaration[];

expect(getSortedNodesNames(sorted)).toEqual([
'XY',
'Xa',
'c',
'g',
'z',
'a',
't',
'k',
'BY',
'Ba',
]);
expect(
sorted
.filter((node) => node.type === 'ImportDeclaration')
.map((importDeclaration) =>
getSortedNodesModulesNames(importDeclaration.specifiers),
),
).toEqual([
['XY'],
['Xa'],
['c', 'cD'],
['g'],
['z'],
['a'],
['tC', 'tA', 'tB'],
['k', 'kE', 'kB'],
['BY'],
['Ba'],
]);
});

test('it returns all sorted nodes with sort order case-insensitive', () => {
const result = getImportNodes(code);
const sorted = getSortedNodesByImportOrder(result, {
importOrder: ['^a$', '^t$', '^k$', '^B'],
importOrderCaseInsensitive: true,
importOrderSeparation: false,
importOrderSortSpecifiers: false,
}) as ImportDeclaration[];
expect(getSortedNodesNames(sorted)).toEqual([
'c',
'g',
'Xa',
'XY',
'z',
'a',
't',
'k',
'Ba',
'BY',
]);
expect(
sorted
.filter((node) => node.type === 'ImportDeclaration')
.map((importDeclaration) =>
getSortedNodesModulesNames(importDeclaration.specifiers),
),
).toEqual([
['c', 'cD'],
['g'],
['Xa'],
['XY'],
['z'],
['a'],
['tC', 'tA', 'tB'],
['k', 'kE', 'kB'],
['Ba'],
['BY'],
]);
});

test('it returns all sorted import nodes with sorted import specifiers', () => {
const result = getImportNodes(code);
const sorted = getSortedNodesByImportOrder(result, {
importOrder: ['^a$', '^t$', '^k$', '^B'],
importOrderCaseInsensitive: false,
importOrderSeparation: false,
importOrderSortSpecifiers: true,
}) as ImportDeclaration[];
expect(getSortedNodesNames(sorted)).toEqual([
'XY',
'Xa',
'c',
'g',
'z',
'a',
't',
'k',
'BY',
'Ba',
]);
expect(
sorted
.filter((node) => node.type === 'ImportDeclaration')
.map((importDeclaration) =>
getSortedNodesModulesNames(importDeclaration.specifiers),
),
).toEqual([
['XY'],
['Xa'],
['c', 'cD'],
['g'],
['z'],
['a'],
['tA', 'tB', 'tC'],
['k', 'kB', 'kE'],
['BY'],
['Ba'],
]);
});

test('it returns all sorted import nodes with sorted import specifiers with case-insensitive ', () => {
const result = getImportNodes(code);
const sorted = getSortedNodesByImportOrder(result, {
importOrder: ['^a$', '^t$', '^k$', '^B'],
importOrderCaseInsensitive: true,
importOrderSeparation: false,
importOrderSortSpecifiers: true,
}) as ImportDeclaration[];
expect(getSortedNodesNames(sorted)).toEqual([
'c',
'g',
'Xa',
'XY',
'z',
'a',
't',
'k',
'Ba',
'BY',
]);
expect(
sorted
.filter((node) => node.type === 'ImportDeclaration')
.map((importDeclaration) =>
getSortedNodesModulesNames(importDeclaration.specifiers),
),
).toEqual([
['c', 'cD'],
['g'],
['Xa'],
['XY'],
['z'],
['a'],
['tA', 'tB', 'tC'],
['k', 'kB', 'kE'],
['Ba'],
['BY'],
]);
});

test('it returns all sorted nodes with custom third party modules', () => {
const result = getImportNodes(code);
const sorted = getSortedNodesByImportOrder(result, {
importOrder: ['^a$', '<THIRD_PARTY_MODULES>', '^t$', '^k$'],
importOrderSeparation: false,
importOrderCaseInsensitive: true,
importOrderSortSpecifiers: false,
}) as ImportDeclaration[];
expect(getSortedNodesNames(sorted)).toEqual([
'a',
'Ba',
'BY',
'c',
'g',
'Xa',
'XY',
'z',
't',
'k',
]);
});
Loading