diff --git a/README.md b/README.md index d35975d..af2832f 100644 --- a/README.md +++ b/README.md @@ -213,6 +213,16 @@ with options as a JSON string of the plugin array: importOrderParserPlugins: [] ``` + +#### `importOrderBuiltinModulesToTop` + +**type**: `boolean` + +**default value:** `false` + +A boolean value to enable sorting of builtins to the top of all import groups. + + ### How does import sort work ? The plugin extracts the imports which are defined in `importOrder`. These imports are considered as _local imports_. diff --git a/src/constants.ts b/src/constants.ts index b1edca6..45c6185 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,5 +1,6 @@ import { ParserPlugin } from '@babel/parser'; import { expressionStatement, stringLiteral } from '@babel/types'; +import { builtinModules } from 'module'; export const flow: ParserPlugin = 'flow'; export const typescript: ParserPlugin = 'typescript'; @@ -15,6 +16,7 @@ export const chunkTypeOther = 'other'; * where the not matched imports should be placed */ export const THIRD_PARTY_MODULES_SPECIAL_WORD = ''; +export const BUILTIN_MODULES = builtinModules.join('|'); const PRETTIER_PLUGIN_SORT_IMPORTS_NEW_LINE = 'PRETTIER_PLUGIN_SORT_IMPORTS_NEW_LINE'; diff --git a/src/preprocessor.ts b/src/preprocessor.ts index a636ce1..285c063 100644 --- a/src/preprocessor.ts +++ b/src/preprocessor.ts @@ -15,6 +15,7 @@ export function preprocessor(code: string, options: PrettierOptions): string { importOrderSeparation, importOrderGroupNamespaceSpecifiers, importOrderSortSpecifiers, + importOrderBuiltinModulesToTop, } = options; const importNodes: ImportDeclaration[] = []; @@ -50,6 +51,7 @@ export function preprocessor(code: string, options: PrettierOptions): string { importOrderSeparation, importOrderGroupNamespaceSpecifiers, importOrderSortSpecifiers, + importOrderBuiltinModulesToTop, }); return getCodeFromAst(allImports, code, directives, interpreter); diff --git a/src/types.ts b/src/types.ts index b4b52ac..8e8567c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -4,6 +4,7 @@ import { RequiredOptions } from 'prettier'; export interface PrettierOptions extends RequiredOptions { importOrder: string[]; importOrderCaseInsensitive: boolean; + importOrderBuiltinModulesToTop: boolean; // should be of type ParserPlugin from '@babel/parser' but prettier does not support nested arrays in options importOrderParserPlugins: string[]; importOrderSeparation: boolean; @@ -24,6 +25,7 @@ export type GetSortedNodes = ( options: Pick< PrettierOptions, | 'importOrder' + | 'importOrderBuiltinModulesToTop' | 'importOrderCaseInsensitive' | 'importOrderSeparation' | 'importOrderGroupNamespaceSpecifiers' 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 ff8f156..1d543aa 100644 --- a/src/utils/__tests__/get-all-comments-from-nodes.spec.ts +++ b/src/utils/__tests__/get-all-comments-from-nodes.spec.ts @@ -14,6 +14,7 @@ const getSortedImportNodes = (code: string, options?: ParserOptions) => { importOrderSeparation: false, importOrderGroupNamespaceSpecifiers: false, importOrderSortSpecifiers: false, + importOrderBuiltinModulesToTop: false, }); }; diff --git a/src/utils/__tests__/get-code-from-ast.spec.ts b/src/utils/__tests__/get-code-from-ast.spec.ts index d64a7d2..e1209fe 100644 --- a/src/utils/__tests__/get-code-from-ast.spec.ts +++ b/src/utils/__tests__/get-code-from-ast.spec.ts @@ -21,6 +21,7 @@ import a from 'a'; importOrderSeparation: false, importOrderGroupNamespaceSpecifiers: false, importOrderSortSpecifiers: false, + importOrderBuiltinModulesToTop: false, }); const formatted = getCodeFromAst(sortedNodes, code, [], undefined); expect(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 2912cb0..b7dd837 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 d488c92..c9a11a9 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 @@ -14,6 +14,9 @@ import { tC, tA, tB } from 't'; import k, { kE, kB } from 'k'; import * as a from 'a'; import * as x from 'x'; +import path from 'path'; +import url from 'node:url'; +import * as fs from "node:fs/promises" import BY from 'BY'; import Ba from 'Ba'; import XY from 'XY'; @@ -28,6 +31,7 @@ test('it returns all sorted nodes', () => { importOrderSeparation: false, importOrderGroupNamespaceSpecifiers: false, importOrderSortSpecifiers: false, + importOrderBuiltinModulesToTop: false, }) as ImportDeclaration[]; expect(getSortedNodesNames(sorted)).toEqual([ @@ -39,6 +43,9 @@ test('it returns all sorted nodes', () => { 'c', 'g', 'k', + 'node:fs/promises', + 'node:url', + 'path', 't', 'x', 'z', @@ -58,6 +65,9 @@ test('it returns all sorted nodes', () => { ['c', 'cD'], ['g'], ['k', 'kE', 'kB'], + ['fs'], + ['url'], + ['path'], ['tC', 'tA', 'tB'], ['x'], ['z'], @@ -72,6 +82,7 @@ test('it returns all sorted nodes case-insensitive', () => { importOrderSeparation: false, importOrderGroupNamespaceSpecifiers: false, importOrderSortSpecifiers: false, + importOrderBuiltinModulesToTop: false, }) as ImportDeclaration[]; expect(getSortedNodesNames(sorted)).toEqual([ @@ -81,6 +92,9 @@ test('it returns all sorted nodes case-insensitive', () => { 'c', 'g', 'k', + 'node:fs/promises', + 'node:url', + 'path', 't', 'x', 'Xa', @@ -100,6 +114,9 @@ test('it returns all sorted nodes case-insensitive', () => { ['c', 'cD'], ['g'], ['k', 'kE', 'kB'], + ['fs'], + ['url'], + ['path'], ['tC', 'tA', 'tB'], ['x'], ['Xa'], @@ -116,6 +133,7 @@ test('it returns all sorted nodes with sort order', () => { importOrderSeparation: false, importOrderGroupNamespaceSpecifiers: false, importOrderSortSpecifiers: false, + importOrderBuiltinModulesToTop: false, }) as ImportDeclaration[]; expect(getSortedNodesNames(sorted)).toEqual([ @@ -123,6 +141,9 @@ test('it returns all sorted nodes with sort order', () => { 'Xa', 'c', 'g', + 'node:fs/promises', + 'node:url', + 'path', 'x', 'z', 'a', @@ -142,6 +163,9 @@ test('it returns all sorted nodes with sort order', () => { ['Xa'], ['c', 'cD'], ['g'], + ['fs'], + ['url'], + ['path'], ['x'], ['z'], ['a'], @@ -160,10 +184,14 @@ test('it returns all sorted nodes with sort order case-insensitive', () => { importOrderSeparation: false, importOrderGroupNamespaceSpecifiers: false, importOrderSortSpecifiers: false, + importOrderBuiltinModulesToTop: false, }) as ImportDeclaration[]; expect(getSortedNodesNames(sorted)).toEqual([ 'c', 'g', + 'node:fs/promises', + 'node:url', + 'path', 'x', 'Xa', 'XY', @@ -183,6 +211,9 @@ test('it returns all sorted nodes with sort order case-insensitive', () => { ).toEqual([ ['c', 'cD'], ['g'], + ['fs'], + ['url'], + ['path'], ['x'], ['Xa'], ['XY'], @@ -203,12 +234,16 @@ test('it returns all sorted import nodes with sorted import specifiers', () => { importOrderSeparation: false, importOrderGroupNamespaceSpecifiers: false, importOrderSortSpecifiers: true, + importOrderBuiltinModulesToTop: false, }) as ImportDeclaration[]; expect(getSortedNodesNames(sorted)).toEqual([ 'XY', 'Xa', 'c', 'g', + 'node:fs/promises', + 'node:url', + 'path', 'x', 'z', 'a', @@ -228,6 +263,9 @@ test('it returns all sorted import nodes with sorted import specifiers', () => { ['Xa'], ['c', 'cD'], ['g'], + ['fs'], + ['url'], + ['path'], ['x'], ['z'], ['a'], @@ -246,10 +284,14 @@ test('it returns all sorted import nodes with sorted import specifiers with case importOrderSeparation: false, importOrderGroupNamespaceSpecifiers: false, importOrderSortSpecifiers: true, + importOrderBuiltinModulesToTop: false, }) as ImportDeclaration[]; expect(getSortedNodesNames(sorted)).toEqual([ 'c', 'g', + 'node:fs/promises', + 'node:url', + 'path', 'x', 'Xa', 'XY', @@ -269,6 +311,9 @@ test('it returns all sorted import nodes with sorted import specifiers with case ).toEqual([ ['c', 'cD'], ['g'], + ['fs'], + ['url'], + ['path'], ['x'], ['Xa'], ['XY'], @@ -289,6 +334,7 @@ test('it returns all sorted nodes with custom third party modules', () => { importOrderCaseInsensitive: true, importOrderGroupNamespaceSpecifiers: false, importOrderSortSpecifiers: false, + importOrderBuiltinModulesToTop: false, }) as ImportDeclaration[]; expect(getSortedNodesNames(sorted)).toEqual([ 'a', @@ -296,6 +342,9 @@ test('it returns all sorted nodes with custom third party modules', () => { 'BY', 'c', 'g', + 'node:fs/promises', + 'node:url', + 'path', 'x', 'Xa', 'XY', @@ -313,10 +362,12 @@ test('it returns all sorted nodes with namespace specifiers at the top', () => { importOrderSeparation: false, importOrderGroupNamespaceSpecifiers: true, importOrderSortSpecifiers: false, + importOrderBuiltinModulesToTop: false, }) as ImportDeclaration[]; expect(getSortedNodesNames(sorted)).toEqual([ 'a', + 'node:fs/promises', 'x', 'BY', 'Ba', @@ -325,7 +376,66 @@ test('it returns all sorted nodes with namespace specifiers at the top', () => { 'c', 'g', 'k', + 'node:url', + 'path', 't', 'z', ]); }); + +test('it returns all sorted nodes with builtin specifiers at the top, ', () => { + const result = getImportNodes(code); + const sorted = getSortedNodes(result, { + importOrder: [], + importOrderCaseInsensitive: false, + importOrderSeparation: false, + importOrderGroupNamespaceSpecifiers: false, + importOrderSortSpecifiers: false, + importOrderBuiltinModulesToTop: true, + }) as ImportDeclaration[]; + + expect(getSortedNodesNames(sorted)).toEqual([ + 'node:fs/promises', + 'node:url', + 'path', + 'BY', + 'Ba', + 'XY', + 'Xa', + 'a', + 'c', + 'g', + 'k', + 't', + 'x', + 'z', + ]); +}); + +test('it returns all sorted nodes with custom third party modules and builtins at top', () => { + const result = getImportNodes(code); + const sorted = getSortedNodes(result, { + importOrder: ['^a$', '', '^t$', '^k$'], + importOrderSeparation: false, + importOrderCaseInsensitive: true, + importOrderGroupNamespaceSpecifiers: false, + importOrderSortSpecifiers: false, + importOrderBuiltinModulesToTop: true, + }) as ImportDeclaration[]; + expect(getSortedNodesNames(sorted)).toEqual([ + 'node:fs/promises', + 'node:url', + 'path', + 'a', + 'Ba', + 'BY', + 'c', + 'g', + 'x', + 'Xa', + 'XY', + 'z', + 't', + 'k', + ]); +}); diff --git a/src/utils/__tests__/get-sorted-nodes.spec.ts b/src/utils/__tests__/get-sorted-nodes.spec.ts index e8cc8c6..63d017f 100644 --- a/src/utils/__tests__/get-sorted-nodes.spec.ts +++ b/src/utils/__tests__/get-sorted-nodes.spec.ts @@ -17,6 +17,7 @@ import "se4"; import "se1"; import * as a from 'a'; import * as x from 'x'; +import path from 'path'; import BY from 'BY'; import Ba from 'Ba'; import XY from 'XY'; @@ -32,6 +33,7 @@ test('it returns all sorted nodes, preserving the order side effect nodes', () = importOrderSeparation: false, importOrderGroupNamespaceSpecifiers: false, importOrderSortSpecifiers: false, + importOrderBuiltinModulesToTop: false, }) as ImportDeclaration[]; expect(getSortedNodesNames(sorted)).toEqual([ @@ -48,6 +50,7 @@ test('it returns all sorted nodes, preserving the order side effect nodes', () = 'XY', 'Xa', 'a', + 'path', 'x', 'se2', ]); @@ -71,6 +74,7 @@ test('it returns all sorted nodes, preserving the order side effect nodes', () = ['XY'], ['Xa'], ['a'], + ['path'], ['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 978f6e1..40c81d4 100644 --- a/src/utils/__tests__/remove-nodes-from-original-code.spec.ts +++ b/src/utils/__tests__/remove-nodes-from-original-code.spec.ts @@ -27,6 +27,7 @@ test('it should remove nodes from the original code', () => { importOrderSeparation: false, importOrderGroupNamespaceSpecifiers: false, importOrderSortSpecifiers: false, + importOrderBuiltinModulesToTop: 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 d8ddd62..ed462dd 100644 --- a/src/utils/get-sorted-nodes-by-import-order.ts +++ b/src/utils/get-sorted-nodes-by-import-order.ts @@ -1,6 +1,10 @@ import { clone } from 'lodash'; -import { THIRD_PARTY_MODULES_SPECIAL_WORD, newLineNode } from '../constants'; +import { + BUILTIN_MODULES, + THIRD_PARTY_MODULES_SPECIAL_WORD, + newLineNode, +} from '../constants'; import { naturalSort } from '../natural-sort'; import { GetSortedNodes, ImportGroups, ImportOrLine } from '../types'; import { getImportNodesMatchedGroup } from './get-import-nodes-matched-group'; @@ -22,6 +26,7 @@ export const getSortedNodesByImportOrder: GetSortedNodes = (nodes, options) => { importOrderSeparation, importOrderSortSpecifiers, importOrderGroupNamespaceSpecifiers, + importOrderBuiltinModulesToTop, } = options; const originalNodes = nodes.map(clone); @@ -31,6 +36,10 @@ export const getSortedNodesByImportOrder: GetSortedNodes = (nodes, options) => { importOrder = [THIRD_PARTY_MODULES_SPECIAL_WORD, ...importOrder]; } + if (importOrderBuiltinModulesToTop) { + importOrder = [BUILTIN_MODULES, ...importOrder]; + } + const importOrderGroups = importOrder.reduce( (groups, regexp) => ({ ...groups,