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: order respects side effect imports #320

Merged
merged 1 commit into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,37 @@ with options as a JSON string of the plugin array:
importOrderParserPlugins: []
```

### `importOrderSideEffects`
**type**: `boolean`
**default value**: `true`

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.

Example:

Initial file:

```js
import z from 'z'
import a from 'a'

import 'side-effect-lib'

import c from 'c'
import b from 'b'
```
When sorted:

```js
import a from 'a'
import z from 'z'

import 'side-effect-lib'

import b from 'b'
import c from 'c'
```

### How does import sort work ?

The plugin extracts the imports which are defined in `importOrder`. These imports are considered as _local imports_.
Expand Down
1 change: 0 additions & 1 deletion examples/example.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import otherthing from '@core/otherthing';
import twoLevelRelativePath from '../../twoLevelRelativePath';
import component from '@ui/hello';


const HelloWorld = ({ name }) => {
return <div>Hello, {name}</div>;
};
Expand Down
3 changes: 3 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ export const newLineCharacters = '\n\n';

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

export const chunkSideEffectNode = 'side-effect-node';
export const chunkSideOtherNode = 'other-node';

/*
* Used to mark the position between RegExps,
* where the not matched imports should be placed
Expand Down
9 changes: 8 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import { parsers as babelParsers } from 'prettier/plugins/babel';
import { parsers as flowParsers } from 'prettier/plugins/flow';
import { parsers as htmlParsers } from 'prettier/plugins/html';
import { parsers as typescriptParsers } from 'prettier/plugins/typescript';

import { defaultPreprocessor } from './preprocessors/default-processor';
import { vuePreprocessor } from './preprocessors/vue-preprocessor';
import { sveltePreprocessor } from './preprocessors/svelte-preprocessor';
import { vuePreprocessor } from './preprocessors/vue-preprocessor';

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

Expand Down Expand Up @@ -50,6 +51,12 @@ const options = {
default: false,
description: 'Should specifiers be sorted?',
},
importOrderSideEffects: {
type: 'boolean',
category: 'Global',
default: true,
description: 'Should side effects be sorted?',
},
};

module.exports = {
Expand Down
2 changes: 2 additions & 0 deletions src/preprocessors/preprocessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export function preprocessor(code: string, options: PrettierOptions) {
importOrderSeparation,
importOrderGroupNamespaceSpecifiers,
importOrderSortSpecifiers,
importOrderSideEffects,
} = options;

const parserOptions: ParserOptions = {
Expand All @@ -42,6 +43,7 @@ export function preprocessor(code: string, options: PrettierOptions) {
importOrderSeparation,
importOrderGroupNamespaceSpecifiers,
importOrderSortSpecifiers,
importOrderSideEffects,
});

return getCodeFromAst(allImports, directives, code, interpreter);
Expand Down
6 changes: 6 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,11 @@ export type GetSortedNodes = (
| 'importOrderSeparation'
| 'importOrderGroupNamespaceSpecifiers'
| 'importOrderSortSpecifiers'
| 'importOrderSideEffects'
>,
) => ImportOrLine[];

export interface ImportChunk {
nodes: ImportDeclaration[];
type: string;
}
107 changes: 107 additions & 0 deletions src/utils/__tests__/adjust-comments-on-sorted-nodes.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { ImportDeclaration } from '@babel/types';

import { adjustCommentsOnSortedNodes } from '../adjust-comments-on-sorted-nodes';
import { getImportNodes } from '../get-import-nodes';

function leadingComments(node: ImportDeclaration): string[] {
return node.leadingComments?.map((c) => c.value) ?? [];
}

function trailingComments(node: ImportDeclaration): string[] {
return node.trailingComments?.map((c) => c.value) ?? [];
}

test('it preserves the single leading comment for each import declaration', () => {
const importNodes = getImportNodes(`
import {x} from "c";
// comment b
import {y} from "b";
// comment a
import {z} from "a";
`);
expect(importNodes).toHaveLength(3);
const finalNodes = [importNodes[2], importNodes[1], importNodes[0]];
adjustCommentsOnSortedNodes(importNodes, finalNodes);
expect(finalNodes).toHaveLength(3);
expect(leadingComments(finalNodes[0])).toEqual([' comment a']);
expect(trailingComments(finalNodes[0])).toEqual([]);
expect(leadingComments(finalNodes[1])).toEqual([' comment b']);
expect(trailingComments(finalNodes[1])).toEqual([]);
expect(leadingComments(finalNodes[2])).toEqual([]);
expect(trailingComments(finalNodes[2])).toEqual([]);
});

test('it preserves multiple leading comments for each import declaration', () => {
const importNodes = getImportNodes(`
import {x} from "c";
// comment b1
// comment b2
// comment b3
import {y} from "b";
// comment a1
// comment a2
// comment a3
import {z} from "a";
`);
expect(importNodes).toHaveLength(3);
const finalNodes = [importNodes[2], importNodes[1], importNodes[0]];
adjustCommentsOnSortedNodes(importNodes, finalNodes);
expect(finalNodes).toHaveLength(3);
expect(leadingComments(finalNodes[0])).toEqual([
' comment a1',
' comment a2',
' comment a3',
]);
expect(trailingComments(finalNodes[0])).toEqual([]);
expect(leadingComments(finalNodes[1])).toEqual([
' comment b1',
' comment b2',
' comment b3',
]);
expect(trailingComments(finalNodes[1])).toEqual([]);
expect(leadingComments(finalNodes[2])).toEqual([]);
expect(trailingComments(finalNodes[2])).toEqual([]);
});

test('it does not move comments at before all import declarations', () => {
const importNodes = getImportNodes(`
// comment c1
// comment c2
import {x} from "c";
import {y} from "b";
import {z} from "a";
`);
expect(importNodes).toHaveLength(3);
const finalNodes = [importNodes[2], importNodes[1], importNodes[0]];
adjustCommentsOnSortedNodes(importNodes, finalNodes);
expect(finalNodes).toHaveLength(3);
expect(leadingComments(finalNodes[0])).toEqual([
' comment c1',
' comment c2',
]);
expect(trailingComments(finalNodes[0])).toEqual([]);
expect(leadingComments(finalNodes[1])).toEqual([]);
expect(trailingComments(finalNodes[1])).toEqual([]);
expect(leadingComments(finalNodes[2])).toEqual([]);
expect(trailingComments(finalNodes[2])).toEqual([]);
});

test('it does not affect comments after all import declarations', () => {
const importNodes = getImportNodes(`
import {x} from "c";
import {y} from "b";
import {z} from "a";
// comment final 1
// comment final 2
`);
expect(importNodes).toHaveLength(3);
const finalNodes = [importNodes[2], importNodes[1], importNodes[0]];
adjustCommentsOnSortedNodes(importNodes, finalNodes);
expect(finalNodes).toHaveLength(3);
expect(leadingComments(finalNodes[0])).toEqual([]);
expect(trailingComments(finalNodes[0])).toEqual([]);
expect(leadingComments(finalNodes[1])).toEqual([]);
expect(trailingComments(finalNodes[1])).toEqual([]);
expect(leadingComments(finalNodes[2])).toEqual([]);
expect(trailingComments(finalNodes[2])).toEqual([]);
});
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) => {
importOrderSeparation: false,
importOrderGroupNamespaceSpecifiers: false,
importOrderSortSpecifiers: false,
importOrderSideEffects: true,
});
};

Expand Down
1 change: 1 addition & 0 deletions src/utils/__tests__/get-code-from-ast.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import a from 'a';
importOrderSeparation: false,
importOrderGroupNamespaceSpecifiers: false,
importOrderSortSpecifiers: false,
importOrderSideEffects: true,
});
const formatted = getCodeFromAst(sortedNodes, [], code, null);
expect(await format(formatted, { parser: 'babel' })).toEqual(
Expand Down
Loading