diff --git a/CHANGELOG.md b/CHANGELOG.md index 009bf02..dbc9e47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,18 @@ ## Changelog + --- +### v3.2.0 +#### New features +- Group Namespace specifiers [#105](https://github.com/trivago/prettier-plugin-sort-imports/pull/105) by [Mattinton](https://github.com/Mattinton) + +#### Chores +- Clean up unit test and snapshot test +- Add contribution guidelines for bug fixes and new features + +### v3.1.1 + +- Fixes package management [#100](https://github.com/trivago/prettier-plugin-sort-imports/issues/100) ### v3.1.0 diff --git a/README.md b/README.md index 3b72e94..53d741d 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,12 @@ A prettier plugin to sort import declarations by provided Regular Expression ord **Note: If you are migrating from v2.x.x to v3.x.x, [Please Read Migration Guidelines](./docs/MIGRATION.md)** ### Input + ![input](./public/images/input-v3-1.png) ### Output -![output](./public/images/output-v3-1.png) +![output](./public/images/output-v3-1.png) ### Install @@ -25,7 +26,6 @@ or, using yarn yarn add --dev @trivago/prettier-plugin-sort-imports ``` - **Note: If you are migrating from v2.x.x to v3.x.x, [Please Read Migration Guidelines](./docs/MIGRATION.md)** ### Usage @@ -47,18 +47,19 @@ module.exports = { ### APIs -#### **`importOrder`** +#### **`importOrder`** **type**: `Array` -A collection of Regular expressions in string format. +A collection of Regular expressions in string format. ``` "importOrder": ["^@core/(.*)$", "^@server/(.*)$", "^@ui/(.*)$", "^[./]"], ``` -_Default behavior:_ The plugin moves the third party imports to the top which are not part of the `importOrder` list. +_Default behavior:_ The plugin moves the third party imports to the top which are not part of the `importOrder` list. To move the third party imports at desired place, you can use `` to assign third party imports to the appropriate position: + ``` "importOrder": ["^@core/(.*)$", "", "^@server/(.*)$", "^@ui/(.*)$", "^[./]"], ``` @@ -69,7 +70,7 @@ To move the third party imports at desired place, you can use `, ) => 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 97c507c..ff8f156 100644 --- a/src/utils/__tests__/get-all-comments-from-nodes.spec.ts +++ b/src/utils/__tests__/get-all-comments-from-nodes.spec.ts @@ -12,6 +12,7 @@ const getSortedImportNodes = (code: string, options?: ParserOptions) => { importOrder: [], importOrderCaseInsensitive: false, importOrderSeparation: false, + importOrderGroupNamespaceSpecifiers: false, importOrderSortSpecifiers: false, }); }; diff --git a/src/utils/__tests__/get-code-from-ast.spec.ts b/src/utils/__tests__/get-code-from-ast.spec.ts index 88666ed..23009ce 100644 --- a/src/utils/__tests__/get-code-from-ast.spec.ts +++ b/src/utils/__tests__/get-code-from-ast.spec.ts @@ -19,6 +19,7 @@ import a from 'a'; importOrder: [], importOrderCaseInsensitive: false, importOrderSeparation: false, + importOrderGroupNamespaceSpecifiers: false, importOrderSortSpecifiers: false, }); const formatted = getCodeFromAst(sortedNodes, code, null); 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 c655a29..d488c92 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 @@ -1,7 +1,7 @@ import { ImportDeclaration } from '@babel/types'; import { getImportNodes } from '../get-import-nodes'; -import { getSortedNodesByImportOrder } from '../get-sorted-nodes-by-import-order'; +import { getSortedNodes } from '../get-sorted-nodes'; import { getSortedNodesModulesNames } from '../get-sorted-nodes-modules-names'; import { getSortedNodesNames } from '../get-sorted-nodes-names'; @@ -13,6 +13,7 @@ import g from 'g'; import { tC, tA, tB } from 't'; import k, { kE, kB } from 'k'; import * as a from 'a'; +import * as x from 'x'; import BY from 'BY'; import Ba from 'Ba'; import XY from 'XY'; @@ -21,10 +22,11 @@ import Xa from 'Xa'; test('it returns all sorted nodes', () => { const result = getImportNodes(code); - const sorted = getSortedNodesByImportOrder(result, { + const sorted = getSortedNodes(result, { importOrder: [], importOrderCaseInsensitive: false, importOrderSeparation: false, + importOrderGroupNamespaceSpecifiers: false, importOrderSortSpecifiers: false, }) as ImportDeclaration[]; @@ -38,6 +40,7 @@ test('it returns all sorted nodes', () => { 'g', 'k', 't', + 'x', 'z', ]); expect( @@ -56,16 +59,18 @@ test('it returns all sorted nodes', () => { ['g'], ['k', 'kE', 'kB'], ['tC', 'tA', 'tB'], + ['x'], ['z'], ]); }); test('it returns all sorted nodes case-insensitive', () => { const result = getImportNodes(code); - const sorted = getSortedNodesByImportOrder(result, { + const sorted = getSortedNodes(result, { importOrder: [], importOrderCaseInsensitive: true, importOrderSeparation: false, + importOrderGroupNamespaceSpecifiers: false, importOrderSortSpecifiers: false, }) as ImportDeclaration[]; @@ -77,6 +82,7 @@ test('it returns all sorted nodes case-insensitive', () => { 'g', 'k', 't', + 'x', 'Xa', 'XY', 'z', @@ -95,6 +101,7 @@ test('it returns all sorted nodes case-insensitive', () => { ['g'], ['k', 'kE', 'kB'], ['tC', 'tA', 'tB'], + ['x'], ['Xa'], ['XY'], ['z'], @@ -103,10 +110,11 @@ test('it returns all sorted nodes case-insensitive', () => { test('it returns all sorted nodes with sort order', () => { const result = getImportNodes(code); - const sorted = getSortedNodesByImportOrder(result, { + const sorted = getSortedNodes(result, { importOrder: ['^a$', '^t$', '^k$', '^B'], importOrderCaseInsensitive: false, importOrderSeparation: false, + importOrderGroupNamespaceSpecifiers: false, importOrderSortSpecifiers: false, }) as ImportDeclaration[]; @@ -115,6 +123,7 @@ test('it returns all sorted nodes with sort order', () => { 'Xa', 'c', 'g', + 'x', 'z', 'a', 't', @@ -133,6 +142,7 @@ test('it returns all sorted nodes with sort order', () => { ['Xa'], ['c', 'cD'], ['g'], + ['x'], ['z'], ['a'], ['tC', 'tA', 'tB'], @@ -144,15 +154,17 @@ test('it returns all sorted nodes with sort order', () => { test('it returns all sorted nodes with sort order case-insensitive', () => { const result = getImportNodes(code); - const sorted = getSortedNodesByImportOrder(result, { + const sorted = getSortedNodes(result, { importOrder: ['^a$', '^t$', '^k$', '^B'], importOrderCaseInsensitive: true, importOrderSeparation: false, + importOrderGroupNamespaceSpecifiers: false, importOrderSortSpecifiers: false, }) as ImportDeclaration[]; expect(getSortedNodesNames(sorted)).toEqual([ 'c', 'g', + 'x', 'Xa', 'XY', 'z', @@ -171,6 +183,7 @@ test('it returns all sorted nodes with sort order case-insensitive', () => { ).toEqual([ ['c', 'cD'], ['g'], + ['x'], ['Xa'], ['XY'], ['z'], @@ -184,10 +197,11 @@ test('it returns all sorted nodes with sort order case-insensitive', () => { test('it returns all sorted import nodes with sorted import specifiers', () => { const result = getImportNodes(code); - const sorted = getSortedNodesByImportOrder(result, { + const sorted = getSortedNodes(result, { importOrder: ['^a$', '^t$', '^k$', '^B'], importOrderCaseInsensitive: false, importOrderSeparation: false, + importOrderGroupNamespaceSpecifiers: false, importOrderSortSpecifiers: true, }) as ImportDeclaration[]; expect(getSortedNodesNames(sorted)).toEqual([ @@ -195,6 +209,7 @@ test('it returns all sorted import nodes with sorted import specifiers', () => { 'Xa', 'c', 'g', + 'x', 'z', 'a', 't', @@ -213,6 +228,7 @@ test('it returns all sorted import nodes with sorted import specifiers', () => { ['Xa'], ['c', 'cD'], ['g'], + ['x'], ['z'], ['a'], ['tA', 'tB', 'tC'], @@ -224,15 +240,17 @@ test('it returns all sorted import nodes with sorted import specifiers', () => { test('it returns all sorted import nodes with sorted import specifiers with case-insensitive ', () => { const result = getImportNodes(code); - const sorted = getSortedNodesByImportOrder(result, { + const sorted = getSortedNodes(result, { importOrder: ['^a$', '^t$', '^k$', '^B'], importOrderCaseInsensitive: true, importOrderSeparation: false, + importOrderGroupNamespaceSpecifiers: false, importOrderSortSpecifiers: true, }) as ImportDeclaration[]; expect(getSortedNodesNames(sorted)).toEqual([ 'c', 'g', + 'x', 'Xa', 'XY', 'z', @@ -251,6 +269,7 @@ test('it returns all sorted import nodes with sorted import specifiers with case ).toEqual([ ['c', 'cD'], ['g'], + ['x'], ['Xa'], ['XY'], ['z'], @@ -264,10 +283,11 @@ test('it returns all sorted import nodes with sorted import specifiers with case test('it returns all sorted nodes with custom third party modules', () => { const result = getImportNodes(code); - const sorted = getSortedNodesByImportOrder(result, { + const sorted = getSortedNodes(result, { importOrder: ['^a$', '', '^t$', '^k$'], importOrderSeparation: false, importOrderCaseInsensitive: true, + importOrderGroupNamespaceSpecifiers: false, importOrderSortSpecifiers: false, }) as ImportDeclaration[]; expect(getSortedNodesNames(sorted)).toEqual([ @@ -276,6 +296,7 @@ test('it returns all sorted nodes with custom third party modules', () => { 'BY', 'c', 'g', + 'x', 'Xa', 'XY', 'z', @@ -283,3 +304,28 @@ test('it returns all sorted nodes with custom third party modules', () => { 'k', ]); }); + +test('it returns all sorted nodes with namespace specifiers at the top', () => { + const result = getImportNodes(code); + const sorted = getSortedNodes(result, { + importOrder: [], + importOrderCaseInsensitive: false, + importOrderSeparation: false, + importOrderGroupNamespaceSpecifiers: true, + importOrderSortSpecifiers: false, + }) as ImportDeclaration[]; + + expect(getSortedNodesNames(sorted)).toEqual([ + 'a', + 'x', + 'BY', + 'Ba', + 'XY', + 'Xa', + 'c', + 'g', + 'k', + 't', + 'z', + ]); +}); diff --git a/src/utils/__tests__/get-sorted-nodes.spec.ts b/src/utils/__tests__/get-sorted-nodes.spec.ts index f532b89..e8cc8c6 100644 --- a/src/utils/__tests__/get-sorted-nodes.spec.ts +++ b/src/utils/__tests__/get-sorted-nodes.spec.ts @@ -16,6 +16,7 @@ import k, { kE, kB } from 'k'; import "se4"; import "se1"; import * as a from 'a'; +import * as x from 'x'; import BY from 'BY'; import Ba from 'Ba'; import XY from 'XY'; @@ -29,6 +30,7 @@ test('it returns all sorted nodes, preserving the order side effect nodes', () = importOrder: [], importOrderCaseInsensitive: false, importOrderSeparation: false, + importOrderGroupNamespaceSpecifiers: false, importOrderSortSpecifiers: false, }) as ImportDeclaration[]; @@ -46,6 +48,7 @@ test('it returns all sorted nodes, preserving the order side effect nodes', () = 'XY', 'Xa', 'a', + 'x', 'se2', ]); expect( @@ -68,6 +71,7 @@ test('it returns all sorted nodes, preserving the order side effect nodes', () = ['XY'], ['Xa'], ['a'], + ['x'], [], ]); }); 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 ed02043..f214cf5 100644 --- a/src/utils/__tests__/remove-nodes-from-original-code.spec.ts +++ b/src/utils/__tests__/remove-nodes-from-original-code.spec.ts @@ -23,6 +23,7 @@ test('it should remove nodes from the original code', () => { importOrder: [], importOrderCaseInsensitive: false, importOrderSeparation: false, + importOrderGroupNamespaceSpecifiers: false, importOrderSortSpecifiers: false, }); 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 fbd93f7..d8ddd62 100644 --- a/src/utils/get-sorted-nodes-by-import-order.ts +++ b/src/utils/get-sorted-nodes-by-import-order.ts @@ -5,6 +5,7 @@ import { naturalSort } from '../natural-sort'; import { GetSortedNodes, ImportGroups, ImportOrLine } from '../types'; import { getImportNodesMatchedGroup } from './get-import-nodes-matched-group'; import { getSortedImportSpecifiers } from './get-sorted-import-specifiers'; +import { getSortedNodesGroup } from './get-sorted-nodes-group'; /** * This function returns the given nodes, sorted in the order as indicated by @@ -17,7 +18,11 @@ export const getSortedNodesByImportOrder: GetSortedNodes = (nodes, options) => { naturalSort.insensitive = options.importOrderCaseInsensitive; let { importOrder } = options; - const { importOrderSeparation, importOrderSortSpecifiers } = options; + const { + importOrderSeparation, + importOrderSortSpecifiers, + importOrderGroupNamespaceSpecifiers, + } = options; const originalNodes = nodes.map(clone); const finalNodes: ImportOrLine[] = []; @@ -46,15 +51,14 @@ export const getSortedNodesByImportOrder: GetSortedNodes = (nodes, options) => { importOrderGroups[matchedGroup].push(node); } - for (let i = 0; i < importOrder.length; i += 1) { - const group = importOrder[i]; + for (const group of importOrder) { const groupNodes = importOrderGroups[group]; if (groupNodes.length === 0) continue; - const sortedInsideGroup = groupNodes.sort((a, b) => - naturalSort(a.source.value, b.source.value), - ); + const sortedInsideGroup = getSortedNodesGroup(groupNodes, { + importOrderGroupNamespaceSpecifiers, + }); // Sort the import specifiers if (importOrderSortSpecifiers) { @@ -65,8 +69,7 @@ export const getSortedNodesByImportOrder: GetSortedNodes = (nodes, options) => { finalNodes.push(...sortedInsideGroup); - // Do not add a new line after the last group of nodes - if (importOrderSeparation && i < importOrder.length - 1) { + if (importOrderSeparation) { finalNodes.push(newLineNode); } } diff --git a/src/utils/get-sorted-nodes-group.ts b/src/utils/get-sorted-nodes-group.ts new file mode 100644 index 0000000..8995133 --- /dev/null +++ b/src/utils/get-sorted-nodes-group.ts @@ -0,0 +1,32 @@ +import { Import, ImportDeclaration } from '@babel/types'; + +import { naturalSort } from '../natural-sort'; +import { PrettierOptions } from '../types'; + +export const getSortedNodesGroup = ( + imports: ImportDeclaration[], + options: Pick, +) => { + return imports.sort((a, b) => { + if (options.importOrderGroupNamespaceSpecifiers) { + const diff = namespaceSpecifierSort(a, b); + if (diff !== 0) return diff; + } + + return naturalSort(a.source.value, b.source.value); + }); +}; + +function namespaceSpecifierSort(a: ImportDeclaration, b: ImportDeclaration) { + const aFirstSpecifier = a.specifiers.find( + (s) => s.type === 'ImportNamespaceSpecifier', + ) + ? 1 + : 0; + const bFirstSpecifier = b.specifiers.find( + (s) => s.type === 'ImportNamespaceSpecifier', + ) + ? 1 + : 0; + return bFirstSpecifier - aFirstSpecifier; +}