Skip to content

Commit a4bba47

Browse files
authored
Merge pull request #214 from trivago/v5
version 5
2 parents 8dd4090 + 3341a50 commit a4bba47

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+3804
-2856
lines changed

.github/workflows/ci.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ jobs:
55
runs-on: ubuntu-latest
66
strategy:
77
matrix:
8-
node: [12.x, 14.x, 16.x, 18.x, 20.x]
8+
node: [18.x, 20.x, 22.x]
99
steps:
10-
- uses: actions/checkout@v2
11-
- uses: actions/setup-node@v2
10+
- uses: actions/checkout@v4
11+
- uses: actions/setup-node@v4
1212
with:
1313
node-version: ${{ matrix.node }}
14-
- run: yarn --frozen-lockfile
14+
- run: yarn --immutable
1515
- run: yarn test

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ logs
44
npm-debug.log*
55
yarn-debug.log*
66
yarn-error.log*
7+
.yarn/install-state.gz
78
lerna-debug.log*
89

910
# Diagnostic reports (https://nodejs.org/api/report.html)

.yarnrc.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
nodeLinker: node-modules

README.md

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,37 @@ with options as a JSON string of the plugin array:
198198
importOrderParserPlugins: []
199199
```
200200

201+
### `importOrderSideEffects`
202+
**type**: `boolean`
203+
**default value**: `true`
204+
205+
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.
206+
207+
Example:
208+
209+
Initial file:
210+
211+
```js
212+
import z from 'z'
213+
import a from 'a'
214+
215+
import 'side-effect-lib'
216+
217+
import c from 'c'
218+
import b from 'b'
219+
```
220+
When sorted:
221+
222+
```js
223+
import a from 'a'
224+
import z from 'z'
225+
226+
import 'side-effect-lib'
227+
228+
import b from 'b'
229+
import c from 'c'
230+
```
231+
201232
### Ignoring import ordering
202233

203234
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.
@@ -209,6 +240,22 @@ import './polyfills';
209240
import foo from 'foo'
210241
```
211242

243+
#### `importOrderImportAttributesKeyword`
244+
245+
**type**: `'assert' | 'with' | 'with-legacy'`
246+
247+
The import attributes/assertions syntax:
248+
- `with`: `import "..." with { type: "json" }`
249+
- `assert`: `import "..." assert { type: "json" }`
250+
- `with-legacy`: `import "..." with type: "json"`.
251+
252+
```json
253+
"importOrderImportAttributesKeyword": 'with'
254+
```
255+
256+
_Default behavior:_ When not specified, @babel/generator will try to match the style in the input code based on the AST shape.
257+
258+
212259
### How does import sort work ?
213260

214261
The plugin extracts the imports which are defined in `importOrder`. These imports are considered as _local imports_.
@@ -234,7 +281,7 @@ Having some trouble or an issue ? You can check [FAQ / Troubleshooting section](
234281
| Solid | ✅ Everything | - |
235282
| Angular | ✅ Everything | Supported through `importOrderParserPlugins` API |
236283
| Vue | ✅ Everything | `@vue/compiler-sfc` is required |
237-
| Svelte | ⚠️ Soon to be supported. | Any contribution is welcome. |
284+
| Svelte | ✅ Everything | `prettier-plugin-svelte` is required |
238285

239286
### Used by
240287

examples/example.jsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import otherthing from '@core/otherthing';
88
import twoLevelRelativePath from '../../twoLevelRelativePath';
99
import component from '@ui/hello';
1010

11-
1211
const HelloWorld = ({ name }) => {
1312
return <div>Hello, {name}</div>;
1413
};

package.json

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@
1414
"compile": "tsc",
1515
"preexample": "yarn run compile",
1616
"example": "prettier --config ./examples/.prettierrc --plugin lib/src/index.js",
17-
"test": "jest -i",
17+
"test": "yarn node --experimental-vm-modules $(yarn bin jest)",
1818
"type-check": "tsc --noEmit",
19-
"prepublishOnly": "npm run compile && npm run test"
19+
"prepublishOnly": "npm run compile && npm run test",
20+
"postinstall": "patch-package"
2021
},
2122
"keywords": [
2223
"prettier",
@@ -33,32 +34,50 @@
3334
},
3435
"license": "Apache-2.0",
3536
"dependencies": {
36-
"@babel/generator": "7.17.7",
37-
"@babel/parser": "^7.20.5",
38-
"@babel/traverse": "7.23.2",
39-
"@babel/types": "7.17.0",
37+
"@babel/generator": "7.26.2",
38+
"@babel/parser": "7.26.2",
39+
"@babel/traverse": "7.25.9",
40+
"@babel/types": "7.26.0",
4041
"javascript-natural-sort": "0.7.1",
4142
"lodash": "^4.17.21"
4243
},
4344
"devDependencies": {
44-
"@babel/core": "^7.20.7",
45-
"@types/chai": "4.2.15",
46-
"@types/jest": "26.0.20",
47-
"@types/lodash": "4.14.168",
48-
"@types/node": "20.8.6",
49-
"@vue/compiler-sfc": "^3.2.41",
50-
"jest": "26.6.3",
51-
"prettier": "2.8",
52-
"ts-jest": "26.5.3",
53-
"typescript": "4.9.4"
45+
"@babel/core": "^7.26.0",
46+
"@types/chai": "5.0.1",
47+
"@types/jest": "29.5.14",
48+
"@types/lodash": "4.17.13",
49+
"@types/node": "22.9.1",
50+
"@vue/compiler-sfc": "^3.5.13",
51+
"jest": "29.7.0",
52+
"patch-package": "^8.0.0",
53+
"postinstall-postinstall": "^2.1.0",
54+
"prettier": "3.3.3",
55+
"prettier-plugin-svelte": "3.3.1",
56+
"svelte": "^4.2.19",
57+
"ts-jest": "29.2.5",
58+
"typescript": "5.6.3"
5459
},
5560
"peerDependencies": {
5661
"@vue/compiler-sfc": "3.x",
57-
"prettier": "2.x - 3.x"
62+
"prettier": "2.x - 3.x",
63+
"prettier-plugin-svelte": "3.x",
64+
"svelte": "4.x"
65+
},
66+
"engines": {
67+
"node": ">18.12"
5868
},
5969
"peerDependenciesMeta": {
6070
"@vue/compiler-sfc": {
6171
"optional": true
72+
},
73+
"prettier-plugin-svelte": {
74+
"optional": true
75+
},
76+
"svelte": {
77+
"optional": true
6278
}
79+
},
80+
"resolutions": {
81+
"@types/babel__generator": "7.6.8"
6382
}
6483
}

src/constants.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,18 @@ export const newLineCharacters = '\n\n';
99

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

12+
export const chunkSideEffectNode = 'side-effect-node';
13+
export const chunkSideOtherNode = 'other-node';
14+
1215
/*
1316
* Used to mark the position between RegExps,
1417
* where the not matched imports should be placed
1518
*/
1619
export const THIRD_PARTY_MODULES_SPECIAL_WORD = '<THIRD_PARTY_MODULES>';
1720

21+
export const THIRD_PARTY_TYPES_SPECIAL_WORD = '<THIRD_PARTY_TS_TYPES>';
22+
export const TYPES_SPECIAL_WORD = '<TS_TYPES>';
23+
1824
const PRETTIER_PLUGIN_SORT_IMPORTS_NEW_LINE =
1925
'PRETTIER_PLUGIN_SORT_IMPORTS_NEW_LINE';
2026

src/index.ts

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1-
import { parsers as babelParsers } from 'prettier/parser-babel';
2-
import { parsers as flowParsers } from 'prettier/parser-flow';
3-
import { parsers as htmlParsers } from 'prettier/parser-html';
4-
import { parsers as typescriptParsers } from 'prettier/parser-typescript';
1+
import { parsers as babelParsers } from 'prettier/plugins/babel';
2+
import { parsers as flowParsers } from 'prettier/plugins/flow';
3+
import { parsers as htmlParsers } from 'prettier/plugins/html';
4+
import { parsers as typescriptParsers } from 'prettier/plugins/typescript';
55

66
import { defaultPreprocessor } from './preprocessors/default-processor';
7+
import { sveltePreprocessor } from './preprocessors/svelte-preprocessor';
78
import { vuePreprocessor } from './preprocessors/vue-preprocessor';
89

10+
const { parsers: svelteParsers } = require('prettier-plugin-svelte');
11+
912
const options = {
1013
importOrder: {
1114
type: 'path',
@@ -48,6 +51,18 @@ const options = {
4851
default: false,
4952
description: 'Should specifiers be sorted?',
5053
},
54+
importOrderSideEffects: {
55+
type: 'boolean',
56+
category: 'Global',
57+
default: true,
58+
description: 'Should side effects be sorted?',
59+
},
60+
importOrderImportAttributesKeyword: {
61+
type: 'string',
62+
category: 'Global',
63+
default: 'with',
64+
description: 'Provide a keyword for import attributes',
65+
}
5166
};
5267

5368
module.exports = {
@@ -68,6 +83,10 @@ module.exports = {
6883
...htmlParsers.vue,
6984
preprocess: vuePreprocessor,
7085
},
86+
svelte: {
87+
...svelteParsers.svelte,
88+
preprocess: sveltePreprocessor,
89+
},
7190
},
7291
options,
7392
};

src/preprocessors/default-processor.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { PrettierOptions } from '../types';
22
import { preprocessor } from './preprocessor';
33

44
export function defaultPreprocessor(code: string, options: PrettierOptions) {
5-
if (options.filepath?.endsWith('.vue')) return code;
5+
for (const extension of ['svelte', 'vue']) {
6+
if (options.filepath?.endsWith(`.${extension}`)) return code;
7+
}
68
return preprocessor(code, options);
79
}

src/preprocessors/preprocessor.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ export function preprocessor(code: string, options: PrettierOptions) {
1616
importOrderSeparation,
1717
importOrderGroupNamespaceSpecifiers,
1818
importOrderSortSpecifiers,
19+
importOrderSideEffects,
20+
importOrderImportAttributesKeyword,
1921
} = options;
2022

2123
const parserOptions: ParserOptions = {
@@ -42,7 +44,10 @@ export function preprocessor(code: string, options: PrettierOptions) {
4244
importOrderSeparation,
4345
importOrderGroupNamespaceSpecifiers,
4446
importOrderSortSpecifiers,
47+
importOrderSideEffects,
4548
});
4649

47-
return getCodeFromAst(allImports, directives, code, interpreter);
50+
return getCodeFromAst(allImports, directives, code, interpreter, {
51+
importOrderImportAttributesKeyword,
52+
});
4853
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { PrettierOptions } from '../types';
2+
import { preprocessor } from './preprocessor';
3+
4+
const booleanGuard = <T>(value: T | undefined): value is T => Boolean(value);
5+
6+
const sortImports = (code: string, options: PrettierOptions) => {
7+
const { parse } = require('svelte/compiler');
8+
const { instance, module } = parse(code);
9+
const sources = [instance, module].filter(booleanGuard);
10+
if (!sources.length) return code;
11+
return sources.reduce((code, source) => {
12+
const snippet = code.slice(source.content.start, source.content.end);
13+
const preprocessed = preprocessor(snippet, options);
14+
const result = code.replace(snippet, `\n${preprocessed}\n`);
15+
return result;
16+
}, code);
17+
};
18+
19+
export function sveltePreprocessor(code: string, options: PrettierOptions) {
20+
const sorted = sortImports(code, options);
21+
22+
const prettierPluginSvelte = require('prettier-plugin-svelte');
23+
return prettierPluginSvelte.parsers.svelte.preprocess(sorted, options);
24+
}

src/preprocessors/vue-preprocessor.ts

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,31 @@ export function vuePreprocessor(code: string, options: PrettierOptions) {
55
const { parse } = require('@vue/compiler-sfc');
66
const { descriptor } = parse(code);
77

8-
const content = (descriptor.script ?? descriptor.scriptSetup)?.content;
9-
if (!content) {
8+
const scriptContent = descriptor.script?.content;
9+
const scriptSetupContent = descriptor.scriptSetup?.content;
10+
11+
if (!scriptContent && !scriptSetupContent) {
1012
return code;
1113
}
1214

13-
return code.replace(content, `\n${preprocessor(content, options)}\n`);
15+
let transformedCode = code;
16+
17+
const replacer = (content: string) => {
18+
// we pass the second argument as a function to avoid issues with the replacement string
19+
// if string contained special groups (like $&, $`, $', $n, $<n>, etc.) this would produce invalid results
20+
return transformedCode.replace(
21+
content,
22+
() => `\n${preprocessor(content, options)}\n`,
23+
);
24+
};
25+
26+
if (scriptContent) {
27+
transformedCode = replacer(scriptContent);
28+
}
29+
30+
if (scriptSetupContent) {
31+
transformedCode = replacer(scriptSetupContent);
32+
}
33+
34+
return transformedCode;
1435
}

src/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,11 @@ export type GetSortedNodes = (
1919
| 'importOrderSeparation'
2020
| 'importOrderGroupNamespaceSpecifiers'
2121
| 'importOrderSortSpecifiers'
22+
| 'importOrderSideEffects'
2223
>,
2324
) => ImportOrLine[];
25+
26+
export interface ImportChunk {
27+
nodes: ImportDeclaration[];
28+
type: string;
29+
}

0 commit comments

Comments
 (0)