diff --git a/README.md b/README.md index 9c29388..081fe18 100644 --- a/README.md +++ b/README.md @@ -198,6 +198,13 @@ with options as a JSON string of the plugin array: importOrderParserPlugins: [] ``` +### `importOrderSortByLength` +**type**: `'asc' | 'desc' | null` +**default value**: `null` + +A choice value to enable sorting imports within their groups based on their string lengths, the two options being ascending and descending. +Leaving the value blank or setting it to null will result in length being ignored + ### `importOrderSideEffects` **type**: `boolean` **default value**: `true` diff --git a/src/index.ts b/src/index.ts index 216f609..d8d3055 100644 --- a/src/index.ts +++ b/src/index.ts @@ -53,6 +53,17 @@ const options: Options = { default: false, description: 'Should specifiers be sorted?', }, + importOrderSortByLength: { + type: 'choice', + category: 'Global', + default: null, + choices: [ + {value: 'asc', description: 'will sort from shortest to longest'}, + {value: 'desc', description: 'will sort from longest to shortest'}, + {value: null, description: 'will disable sorting based on length'} + ], + description: 'Should imports be sorted by their string length' + }, importOrderSideEffects: { type: 'boolean', category: 'Global', @@ -64,7 +75,7 @@ const options: Options = { category: 'Global', default: 'with', description: 'Provide a keyword for import attributes', - }, + } }; module.exports = { diff --git a/src/preprocessors/preprocessor.ts b/src/preprocessors/preprocessor.ts index 716212e..233ad66 100644 --- a/src/preprocessors/preprocessor.ts +++ b/src/preprocessors/preprocessor.ts @@ -16,8 +16,9 @@ export function preprocessor(code: string, options: PrettierOptions) { importOrderSeparation, importOrderGroupNamespaceSpecifiers, importOrderSortSpecifiers, + importOrderSortByLength, importOrderSideEffects, - importOrderImportAttributesKeyword, + importOrderImportAttributesKeyword } = options; const parserOptions: ParserOptions = { @@ -44,7 +45,8 @@ export function preprocessor(code: string, options: PrettierOptions) { importOrderSeparation, importOrderGroupNamespaceSpecifiers, importOrderSortSpecifiers, - importOrderSideEffects, + importOrderSortByLength, + importOrderSideEffects }); return getCodeFromAst(allImports, directives, code, interpreter, { diff --git a/src/types.ts b/src/types.ts index 085c0b1..799e4e0 100644 --- a/src/types.ts +++ b/src/types.ts @@ -19,6 +19,7 @@ export type GetSortedNodes = ( | 'importOrderSeparation' | 'importOrderGroupNamespaceSpecifiers' | 'importOrderSortSpecifiers' + | 'importOrderSortByLength' | 'importOrderSideEffects' >, ) => ImportOrLine[]; diff --git a/src/utils/__tests__/get-all-comments-from-nodes.spec.ts b/src/utils/__tests__/get-all-comments-from-nodes.spec.ts index c076e92..6a2813a 100644 --- a/src/utils/__tests__/get-all-comments-from-nodes.spec.ts +++ b/src/utils/__tests__/get-all-comments-from-nodes.spec.ts @@ -14,7 +14,8 @@ const getSortedImportNodes = (code: string, options?: ParserOptions) => { importOrderSeparation: false, importOrderGroupNamespaceSpecifiers: false, importOrderSortSpecifiers: false, - importOrderSideEffects: true, + importOrderSortByLength: null, + importOrderSideEffects: true }); }; diff --git a/src/utils/__tests__/get-code-from-ast.spec.ts b/src/utils/__tests__/get-code-from-ast.spec.ts index 298e964..05101f8 100644 --- a/src/utils/__tests__/get-code-from-ast.spec.ts +++ b/src/utils/__tests__/get-code-from-ast.spec.ts @@ -25,7 +25,8 @@ import a from 'a'; importOrderSeparation: false, importOrderGroupNamespaceSpecifiers: false, importOrderSortSpecifiers: false, - importOrderSideEffects: true, + importOrderSortByLength: null, + importOrderSideEffects: true }); const formatted = getCodeFromAst(sortedNodes, [], code, null); expect(await format(formatted, { parser: 'babel' })).toEqual( diff --git a/src/utils/__tests__/get-import-nodes-matched-group.spec.ts b/src/utils/__tests__/get-import-nodes-matched-group.spec.ts index b585eb4..11ec6b2 100644 --- a/src/utils/__tests__/get-import-nodes-matched-group.spec.ts +++ b/src/utils/__tests__/get-import-nodes-matched-group.spec.ts @@ -1,5 +1,3 @@ -import { THIRD_PARTY_MODULES_SPECIAL_WORD } from '../../constants'; -import { ImportGroups } from '../../types'; import { getImportNodes } from '../get-import-nodes'; import { getImportNodesMatchedGroup } from '../get-import-nodes-matched-group'; diff --git a/src/utils/__tests__/get-sorted-nodes-by-import-order.spec.ts b/src/utils/__tests__/get-sorted-nodes-by-import-order.spec.ts index cc861b6..5461d80 100644 --- a/src/utils/__tests__/get-sorted-nodes-by-import-order.spec.ts +++ b/src/utils/__tests__/get-sorted-nodes-by-import-order.spec.ts @@ -28,6 +28,7 @@ test('it returns all sorted nodes', () => { importOrderSeparation: false, importOrderGroupNamespaceSpecifiers: false, importOrderSortSpecifiers: false, + importOrderSortByLength: null, importOrderSideEffects: true, }) as ImportDeclaration[]; @@ -73,6 +74,7 @@ test('it returns all sorted nodes case-insensitive', () => { importOrderSeparation: false, importOrderGroupNamespaceSpecifiers: false, importOrderSortSpecifiers: false, + importOrderSortByLength: null, importOrderSideEffects: true, }) as ImportDeclaration[]; @@ -118,6 +120,7 @@ test('it returns all sorted nodes with sort order', () => { importOrderSeparation: false, importOrderGroupNamespaceSpecifiers: false, importOrderSortSpecifiers: false, + importOrderSortByLength: null, importOrderSideEffects: true, }) as ImportDeclaration[]; @@ -163,6 +166,7 @@ test('it returns all sorted nodes with sort order case-insensitive', () => { importOrderSeparation: false, importOrderGroupNamespaceSpecifiers: false, importOrderSortSpecifiers: false, + importOrderSortByLength: null, importOrderSideEffects: true, }) as ImportDeclaration[]; expect(getSortedNodesNames(sorted)).toEqual([ @@ -207,6 +211,7 @@ test('it returns all sorted import nodes with sorted import specifiers', () => { importOrderSeparation: false, importOrderGroupNamespaceSpecifiers: false, importOrderSortSpecifiers: true, + importOrderSortByLength: null, importOrderSideEffects: true, }) as ImportDeclaration[]; expect(getSortedNodesNames(sorted)).toEqual([ @@ -251,6 +256,7 @@ test('it returns all sorted import nodes with sorted import specifiers with case importOrderSeparation: false, importOrderGroupNamespaceSpecifiers: false, importOrderSortSpecifiers: true, + importOrderSortByLength: null, importOrderSideEffects: true, }) as ImportDeclaration[]; expect(getSortedNodesNames(sorted)).toEqual([ @@ -295,6 +301,7 @@ test('it returns all sorted nodes with custom third party modules', () => { importOrderCaseInsensitive: true, importOrderGroupNamespaceSpecifiers: false, importOrderSortSpecifiers: false, + importOrderSortByLength: null, importOrderSideEffects: true, }) as ImportDeclaration[]; expect(getSortedNodesNames(sorted)).toEqual([ @@ -320,6 +327,7 @@ test('it returns all sorted nodes with namespace specifiers at the top', () => { importOrderSeparation: false, importOrderGroupNamespaceSpecifiers: true, importOrderSortSpecifiers: false, + importOrderSortByLength: null, importOrderSideEffects: true, }) as ImportDeclaration[]; diff --git a/src/utils/__tests__/get-sorted-nodes.spec.ts b/src/utils/__tests__/get-sorted-nodes.spec.ts index 17dca35..ae17f0f 100644 --- a/src/utils/__tests__/get-sorted-nodes.spec.ts +++ b/src/utils/__tests__/get-sorted-nodes.spec.ts @@ -46,7 +46,8 @@ test('it returns all sorted nodes', () => { importOrderSeparation: false, importOrderGroupNamespaceSpecifiers: false, importOrderSortSpecifiers: false, - importOrderSideEffects: true, + importOrderSortByLength: null, + importOrderSideEffects: true }) as ImportDeclaration[]; expect(getSortedNodesNames(sorted)).toEqual([ @@ -91,7 +92,8 @@ test('it returns all sorted nodes case-insensitive', () => { importOrderSeparation: false, importOrderGroupNamespaceSpecifiers: false, importOrderSortSpecifiers: false, - importOrderSideEffects: true, + importOrderSortByLength: null, + importOrderSideEffects: true }) as ImportDeclaration[]; expect(getSortedNodesNames(sorted)).toEqual([ @@ -136,7 +138,8 @@ test('it returns all sorted nodes with sort order', () => { importOrderSeparation: false, importOrderGroupNamespaceSpecifiers: false, importOrderSortSpecifiers: false, - importOrderSideEffects: true, + importOrderSortByLength: null, + importOrderSideEffects: true }) as ImportDeclaration[]; expect(getSortedNodesNames(sorted)).toEqual([ @@ -181,7 +184,8 @@ test('it returns all sorted nodes with sort order case-insensitive', () => { importOrderSeparation: false, importOrderGroupNamespaceSpecifiers: false, importOrderSortSpecifiers: false, - importOrderSideEffects: true, + importOrderSortByLength: null, + importOrderSideEffects: true }) as ImportDeclaration[]; expect(getSortedNodesNames(sorted)).toEqual([ 'c', @@ -225,7 +229,8 @@ test('it returns all sorted import nodes with sorted import specifiers', () => { importOrderSeparation: false, importOrderGroupNamespaceSpecifiers: false, importOrderSortSpecifiers: true, - importOrderSideEffects: true, + importOrderSortByLength: null, + importOrderSideEffects: true }) as ImportDeclaration[]; expect(getSortedNodesNames(sorted)).toEqual([ 'XY', @@ -269,7 +274,8 @@ test('it returns all sorted import nodes with sorted import specifiers with case importOrderSeparation: false, importOrderGroupNamespaceSpecifiers: false, importOrderSortSpecifiers: true, - importOrderSideEffects: true, + importOrderSortByLength: null, + importOrderSideEffects: true }) as ImportDeclaration[]; expect(getSortedNodesNames(sorted)).toEqual([ 'c', @@ -313,7 +319,8 @@ test('it returns all sorted nodes with custom third party modules', () => { importOrderCaseInsensitive: true, importOrderGroupNamespaceSpecifiers: false, importOrderSortSpecifiers: false, - importOrderSideEffects: true, + importOrderSortByLength: null, + importOrderSideEffects: true }) as ImportDeclaration[]; expect(getSortedNodesNames(sorted)).toEqual([ 'a', @@ -338,7 +345,8 @@ test('it returns all sorted nodes with namespace specifiers at the top', () => { importOrderSeparation: false, importOrderGroupNamespaceSpecifiers: true, importOrderSortSpecifiers: false, - importOrderSideEffects: true, + importOrderSortByLength: null, + importOrderSideEffects: true }) as ImportDeclaration[]; expect(getSortedNodesNames(sorted)).toEqual([ @@ -356,6 +364,58 @@ test('it returns all sorted nodes with namespace specifiers at the top', () => { ]); }); +test('it returns all sorted nodes, sorted shortest to longest', () => { + const result = getImportNodes(code) + const sorted = getSortedNodes(result, { + importOrder: [], + importOrderCaseInsensitive: false, + importOrderSeparation: false, + importOrderGroupNamespaceSpecifiers: false, + importOrderSortSpecifiers: false, + importOrderSideEffects: true, + importOrderSortByLength: 'asc' + }) as ImportDeclaration[]; + expect(getSortedNodesNames(sorted)).toEqual([ + 'g', + 'z', + 'Ba', + 'BY', + 'Xa', + 'XY', + 'a', + 'x', + 'c', + 'k', + 't', + ]); +}); + +test('it returns all sorted nodes, sorted longest to shortest', () => { + const result = getImportNodes(code) + const sorted = getSortedNodes(result, { + importOrder: [], + importOrderCaseInsensitive: false, + importOrderSeparation: false, + importOrderGroupNamespaceSpecifiers: false, + importOrderSortSpecifiers: false, + importOrderSideEffects: false, + importOrderSortByLength: 'desc' + }) as ImportDeclaration[]; + expect(getSortedNodesNames(sorted)).toEqual([ + 't', + 'k', + 'c', + 'a', + 'x', + 'Ba', + 'BY', + 'Xa', + 'XY', + 'g', + 'z' + ]); +}); + test('it returns all sorted nodes with types', () => { const result = getImportNodes(typeCode, { plugins: ['typescript'], @@ -367,6 +427,7 @@ test('it returns all sorted nodes with types', () => { importOrderGroupNamespaceSpecifiers: false, importOrderSortSpecifiers: false, importOrderSideEffects: true, + importOrderSortByLength: null, }) as ImportDeclaration[]; expect(getSortedNodesNames(sorted)).toEqual([ diff --git a/src/utils/__tests__/remove-nodes-from-original-code.spec.ts b/src/utils/__tests__/remove-nodes-from-original-code.spec.ts index 21dd785..6a8b947 100644 --- a/src/utils/__tests__/remove-nodes-from-original-code.spec.ts +++ b/src/utils/__tests__/remove-nodes-from-original-code.spec.ts @@ -25,7 +25,8 @@ test('it should remove nodes from the original code', async () => { importOrderSeparation: false, importOrderGroupNamespaceSpecifiers: false, importOrderSortSpecifiers: false, - importOrderSideEffects: true, + importOrderSortByLength: null, + importOrderSideEffects: true }); const allCommentsFromImports = getAllCommentsFromNodes(sortedNodes); diff --git a/src/utils/get-sorted-nodes-by-import-order.ts b/src/utils/get-sorted-nodes-by-import-order.ts index d8ddd62..676a700 100644 --- a/src/utils/get-sorted-nodes-by-import-order.ts +++ b/src/utils/get-sorted-nodes-by-import-order.ts @@ -17,7 +17,7 @@ import { getSortedNodesGroup } from './get-sorted-nodes-group'; export const getSortedNodesByImportOrder: GetSortedNodes = (nodes, options) => { naturalSort.insensitive = options.importOrderCaseInsensitive; - let { importOrder } = options; + let { importOrder,importOrderSortByLength } = options; const { importOrderSeparation, importOrderSortSpecifiers, @@ -58,6 +58,7 @@ export const getSortedNodesByImportOrder: GetSortedNodes = (nodes, options) => { const sortedInsideGroup = getSortedNodesGroup(groupNodes, { importOrderGroupNamespaceSpecifiers, + importOrderSortByLength }); // Sort the import specifiers diff --git a/src/utils/get-sorted-nodes-group.ts b/src/utils/get-sorted-nodes-group.ts index 8995133..beb4776 100644 --- a/src/utils/get-sorted-nodes-group.ts +++ b/src/utils/get-sorted-nodes-group.ts @@ -1,18 +1,27 @@ -import { Import, ImportDeclaration } from '@babel/types'; +import { ImportDeclaration } from '@babel/types'; import { naturalSort } from '../natural-sort'; import { PrettierOptions } from '../types'; export const getSortedNodesGroup = ( imports: ImportDeclaration[], - options: Pick, + options: Pick, ) => { return imports.sort((a, b) => { + const aLength = (a.end || 0) - (a.start || 0) + const bLength = (b.end || 0) - (b.start || 0) + if (options.importOrderGroupNamespaceSpecifiers) { const diff = namespaceSpecifierSort(a, b); if (diff !== 0) return diff; } + if (options.importOrderSortByLength === 'asc') + return aLength - bLength || a.source.value.localeCompare(b.source.value) + + if (options.importOrderSortByLength === 'desc') + return bLength - aLength || a.source.value.localeCompare(b.source.value) + return naturalSort(a.source.value, b.source.value); }); }; diff --git a/types/index.d.ts b/types/index.d.ts index 46bc56e..c132ae5 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -47,21 +47,21 @@ export interface PluginConfig { /** * A boolean value to enable case-insensitivity in the sorting algorithm used to order imports within each match group. - * + * * For example, when false (or not specified): - * + * * ```js * import ExampleView from './ExampleView'; * import ExamplesList from './ExamplesList'; * ``` - * + * * compared with `"importOrderCaseInsensitive": true`: - * + * * ```js * import ExamplesList from './ExamplesList'; * import ExampleView from './ExampleView'; * ``` - * + * * @default false */ importOrderCaseInsensitive?: boolean; @@ -93,6 +93,26 @@ used to order imports within each match group. */ importOrderParserPlugins?: ImportOrderParserPlugin[]; + /** + * A choice value to enable sorting imports within their groups based on their string lengths, the two options being ascending and descending. + * Leaving the value blank or setting it to null will result in length being ignored + * + * @default null + */ + importOrderSortByLength?: 'asc' | 'desc' | null + + /** + * By default, the plugin sorts side effect imports 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. + * + * ``` + * "importOrderImportAttributesKeyword": 'with', + * ``` + * + * @default true + */ + importOrderSideEffects?: boolean; /** * The import attributes/assertions syntax to use. "with" for import "..." with { type: "json" },