Skip to content

Commit 3347f3f

Browse files
Merge pull request #381 from sdotson/add-sortNodeBuiltinModulesToTop
Add <BUILTIN_MODULES> option for Node.js builtin modules
2 parents e750c32 + dfbb6cb commit 3347f3f

20 files changed

Lines changed: 590 additions & 151 deletions

File tree

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22

33

44
---
5+
### 5.3.0
6+
#### Breaking Changes
7+
- **BREAKING:** Removed `importOrderBuiltinModulesToTop` option in favor of using `<BUILTIN_MODULES>` placeholder in `importOrder` array [#381](https://github.com/trivago/prettier-plugin-sort-imports/pull/381). This provides more flexibility to control where builtin modules are positioned.
8+
- **Migration:** Replace `importOrderBuiltinModulesToTop: true` with adding `<BUILTIN_MODULES>` to your `importOrder` array.
9+
- Before: `{ importOrderBuiltinModulesToTop: true, importOrder: ['^[./]'] }`
10+
- After: `{ importOrder: ['<BUILTIN_MODULES>', '<THIRD_PARTY_MODULES>', '^[./]'] }`
11+
12+
#### New features
13+
- Add `<BUILTIN_MODULES>` special word to control Node.js builtin module positioning [#71](https://github.com/trivago/prettier-plugin-sort-imports/issues/71) - Support for both traditional (`fs`, `path`) and `node:` prefixed (`node:fs`, `node:path`) builtin modules
14+
515
### 5.2.2
616
- Update packages and pin babel/types [#343](https://github.com/trivago/prettier-plugin-sort-imports/pull/343) by [@byara](https://github.com/byara)
717

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,14 @@ To move the third party imports at desired place, you can use `<THIRD_PARTY_MODU
120120
"importOrder": ["^@core/(.*)$", "<THIRD_PARTY_MODULES>", "^@server/(.*)$", "^@ui/(.*)$", "^[./]"],
121121
```
122122

123+
You can also use `<BUILTIN_MODULES>` to control the position of Node.js builtin modules (like `fs`, `path`, `http`, and their `node:` prefixed variants):
124+
125+
```
126+
"importOrder": ["<BUILTIN_MODULES>", "<THIRD_PARTY_MODULES>", "^@core/(.*)$", "^@server/(.*)$", "^@ui/(.*)$", "^[./]"],
127+
```
128+
129+
When `<BUILTIN_MODULES>` is included in your `importOrder`, Node.js builtin modules will be sorted to that position. If not included, builtin modules are treated as regular third-party imports.
130+
123131
#### `importOrderSeparation`
124132

125133
**type**: `boolean`
@@ -245,6 +253,7 @@ import b from 'b'
245253
import c from 'c'
246254
```
247255

256+
248257
### Ignoring import ordering
249258

250259
In some cases it's desired to ignore import ordering, specifically if you require to instantiate a common service or polyfill in your application logic before all the other imports. The plugin supports the `// sort-imports-ignore` comment, which will exclude the file from ordering the imports.

src/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ export const chunkSideOtherNode = 'other-node';
1818
*/
1919
export const THIRD_PARTY_MODULES_SPECIAL_WORD = '<THIRD_PARTY_MODULES>';
2020

21+
export const BUILTIN_MODULES_SPECIAL_WORD = '<BUILTIN_MODULES>';
22+
2123
export const THIRD_PARTY_TYPES_SPECIAL_WORD = '<THIRD_PARTY_TS_TYPES>';
2224
export const TYPES_SPECIAL_WORD = '<TS_TYPES>';
2325
export const SEPARATOR_SPECIAL_WORD = '<SEPARATOR>';
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { describe, expect, it } from 'vitest';
2+
3+
import { isBuiltinModule } from '../is-builtin-module.js';
4+
5+
describe('isBuiltinModule', () => {
6+
it('should correctly identify traditional builtin modules', () => {
7+
expect(isBuiltinModule('fs')).toBe(true);
8+
expect(isBuiltinModule('path')).toBe(true);
9+
expect(isBuiltinModule('http')).toBe(true);
10+
expect(isBuiltinModule('crypto')).toBe(true);
11+
expect(isBuiltinModule('util')).toBe(true);
12+
expect(isBuiltinModule('os')).toBe(true);
13+
expect(isBuiltinModule('events')).toBe(true);
14+
});
15+
16+
it('should correctly identify node: prefixed builtin modules', () => {
17+
expect(isBuiltinModule('node:fs')).toBe(true);
18+
expect(isBuiltinModule('node:path')).toBe(true);
19+
expect(isBuiltinModule('node:http')).toBe(true);
20+
expect(isBuiltinModule('node:crypto')).toBe(true);
21+
expect(isBuiltinModule('node:util')).toBe(true);
22+
expect(isBuiltinModule('node:os')).toBe(true);
23+
expect(isBuiltinModule('node:events')).toBe(true);
24+
});
25+
26+
it('should return false for non-builtin modules', () => {
27+
expect(isBuiltinModule('express')).toBe(false);
28+
expect(isBuiltinModule('lodash')).toBe(false);
29+
expect(isBuiltinModule('react')).toBe(false);
30+
expect(isBuiltinModule('@types/node')).toBe(false);
31+
expect(isBuiltinModule('./my-module')).toBe(false);
32+
expect(isBuiltinModule('../other-module')).toBe(false);
33+
});
34+
35+
it('should return false for invalid node: prefixes', () => {
36+
expect(isBuiltinModule('node:express')).toBe(false);
37+
expect(isBuiltinModule('node:lodash')).toBe(false);
38+
expect(isBuiltinModule('node:nonexistent')).toBe(false);
39+
});
40+
41+
it('should handle edge cases', () => {
42+
expect(isBuiltinModule('')).toBe(false);
43+
expect(isBuiltinModule('node:')).toBe(false);
44+
expect(isBuiltinModule('node')).toBe(false);
45+
});
46+
});

src/utils/get-import-nodes-matched-group.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { ImportDeclaration } from '@babel/types';
22

33
import {
4+
BUILTIN_MODULES_SPECIAL_WORD,
45
THIRD_PARTY_MODULES_SPECIAL_WORD,
56
THIRD_PARTY_TYPES_SPECIAL_WORD,
67
TYPES_SPECIAL_WORD,
78
} from '../constants.js';
9+
import { isBuiltinModule } from './is-builtin-module.js';
810

911
/**
1012
* Get the regexp group to keep the import nodes.
@@ -15,6 +17,16 @@ export const getImportNodesMatchedGroup = (
1517
node: ImportDeclaration,
1618
importOrder: string[],
1719
) => {
20+
const moduleName = node.source.value;
21+
22+
// Check if this is a builtin module and <BUILTIN_MODULES> is in the importOrder
23+
if (
24+
importOrder.includes(BUILTIN_MODULES_SPECIAL_WORD) &&
25+
isBuiltinModule(moduleName)
26+
) {
27+
return BUILTIN_MODULES_SPECIAL_WORD;
28+
}
29+
1830
const groupWithRegExp = importOrder.map((group) => ({
1931
group,
2032
regExp: group.startsWith(TYPES_SPECIAL_WORD)

src/utils/get-sorted-nodes-by-import-order.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { clone } from 'lodash-es';
22

33
import {
4+
BUILTIN_MODULES_SPECIAL_WORD,
45
SEPARATOR_SPECIAL_WORD,
56
THIRD_PARTY_MODULES_SPECIAL_WORD,
67
newLineNode,
@@ -43,15 +44,14 @@ export const getSortedNodesByImportOrder: GetSortedNodes = (nodes, options) => {
4344
{},
4445
);
4546

46-
const importOrderWithOutThirdPartyPlaceholder = importOrder.filter(
47-
(group) => group !== THIRD_PARTY_MODULES_SPECIAL_WORD,
47+
const importOrderWithOutSpecialWords = importOrder.filter(
48+
(group) =>
49+
group !== THIRD_PARTY_MODULES_SPECIAL_WORD &&
50+
group !== BUILTIN_MODULES_SPECIAL_WORD,
4851
);
4952

5053
for (const node of originalNodes) {
51-
const matchedGroup = getImportNodesMatchedGroup(
52-
node,
53-
importOrderWithOutThirdPartyPlaceholder,
54-
);
54+
const matchedGroup = getImportNodesMatchedGroup(node, importOrder);
5555
importOrderGroups[matchedGroup].push(node);
5656
}
5757

src/utils/is-builtin-module.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { builtinModules } from 'module';
2+
3+
/**
4+
* Check if a module name is a Node.js builtin module.
5+
* This includes both the traditional names (e.g., 'fs') and the
6+
* new node: prefixed names (e.g., 'node:fs').
7+
*
8+
* @param moduleName The module name to check
9+
* @returns True if the module is a Node.js builtin module
10+
*/
11+
export const isBuiltinModule = (moduleName: string): boolean => {
12+
// Handle node: prefixed modules
13+
if (moduleName.startsWith('node:')) {
14+
const withoutPrefix = moduleName.slice(5);
15+
return builtinModules.includes(withoutPrefix);
16+
}
17+
18+
// Handle traditional module names
19+
return builtinModules.includes(moduleName);
20+
};

test-setup/run_spec.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ function run_spec(dirname, parsers, options) {
4646
).toMatchSnapshot(filename);
4747
} catch (e) {
4848
throw new Error(
49-
`Problem occurred in ${path} file: ${error.name}`,
49+
`Problem occurred in ${path} file: ${e.name}`,
5050
);
5151
}
5252
});
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2+
3+
exports[`builtin-modules.ts - typescript-verify > builtin-modules.ts 1`] = `
4+
import express from 'express';
5+
import lodash from 'lodash';
6+
import fs from 'fs';
7+
import path from 'path';
8+
import http from 'http';
9+
import { readFile } from 'node:fs';
10+
import { join } from 'node:path';
11+
import crypto from 'node:crypto';
12+
import util from 'util';
13+
14+
import { myFunction } from './my-module';
15+
import { anotherFunction } from '../other-module';~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
16+
import fs from "fs";
17+
import http from "http";
18+
import crypto from "node:crypto";
19+
import { readFile } from "node:fs";
20+
import { join } from "node:path";
21+
import path from "path";
22+
import util from "util";
23+
import express from "express";
24+
import lodash from "lodash";
25+
import { anotherFunction } from "../other-module";
26+
import { myFunction } from "./my-module";
27+
28+
`;
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import express from 'express';
2+
import lodash from 'lodash';
3+
import fs from 'fs';
4+
import path from 'path';
5+
import http from 'http';
6+
import { readFile } from 'node:fs';
7+
import { join } from 'node:path';
8+
import crypto from 'node:crypto';
9+
import util from 'util';
10+
11+
import { myFunction } from './my-module';
12+
import { anotherFunction } from '../other-module';

0 commit comments

Comments
 (0)