Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node: [12.x, 14.x, 16.x, 18.x, 20.x]
node: [18.x, 20.x, 22.x]
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- run: yarn --frozen-lockfile
- run: yarn --immutable
- run: yarn test
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.yarn/install-state.gz
lerna-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
Expand Down
1 change: 1 addition & 0 deletions .yarnrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
nodeLinker: node-modules
49 changes: 48 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,37 @@ with options as a JSON string of the plugin array:
importOrderParserPlugins: []
```

### `importOrderSideEffects`
**type**: `boolean`
**default value**: `true`

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.

Example:

Initial file:

```js
import z from 'z'
import a from 'a'

import 'side-effect-lib'

import c from 'c'
import b from 'b'
```
When sorted:

```js
import a from 'a'
import z from 'z'

import 'side-effect-lib'

import b from 'b'
import c from 'c'
```

### Ignoring import ordering

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

#### `importOrderImportAttributesKeyword`

**type**: `'assert' | 'with' | 'with-legacy'`

The import attributes/assertions syntax:
- `with`: `import "..." with { type: "json" }`
- `assert`: `import "..." assert { type: "json" }`
- `with-legacy`: `import "..." with type: "json"`.

```json
"importOrderImportAttributesKeyword": 'with'
```

_Default behavior:_ When not specified, @babel/generator will try to match the style in the input code based on the AST shape.


### How does import sort work ?

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

### Used by

Expand Down
1 change: 0 additions & 1 deletion examples/example.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import otherthing from '@core/otherthing';
import twoLevelRelativePath from '../../twoLevelRelativePath';
import component from '@ui/hello';


const HelloWorld = ({ name }) => {
return <div>Hello, {name}</div>;
};
Expand Down
53 changes: 36 additions & 17 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@
"compile": "tsc",
"preexample": "yarn run compile",
"example": "prettier --config ./examples/.prettierrc --plugin lib/src/index.js",
"test": "jest -i",
"test": "yarn node --experimental-vm-modules $(yarn bin jest)",
"type-check": "tsc --noEmit",
"prepublishOnly": "npm run compile && npm run test"
"prepublishOnly": "npm run compile && npm run test",
"postinstall": "patch-package"
},
"keywords": [
"prettier",
Expand All @@ -33,32 +34,50 @@
},
"license": "Apache-2.0",
"dependencies": {
"@babel/generator": "7.17.7",
"@babel/parser": "^7.20.5",
"@babel/traverse": "7.23.2",
"@babel/types": "7.17.0",
"@babel/generator": "7.26.2",
"@babel/parser": "7.26.2",
"@babel/traverse": "7.25.9",
"@babel/types": "7.26.0",
"javascript-natural-sort": "0.7.1",
"lodash": "^4.17.21"
},
"devDependencies": {
"@babel/core": "^7.20.7",
"@types/chai": "4.2.15",
"@types/jest": "26.0.20",
"@types/lodash": "4.14.168",
"@types/node": "20.8.6",
"@vue/compiler-sfc": "^3.2.41",
"jest": "26.6.3",
"prettier": "2.8",
"ts-jest": "26.5.3",
"typescript": "4.9.4"
"@babel/core": "^7.26.0",
"@types/chai": "5.0.1",
"@types/jest": "29.5.14",
"@types/lodash": "4.17.13",
"@types/node": "22.9.1",
"@vue/compiler-sfc": "^3.5.13",
"jest": "29.7.0",
"patch-package": "^8.0.0",
"postinstall-postinstall": "^2.1.0",
"prettier": "3.3.3",
"prettier-plugin-svelte": "3.3.1",
"svelte": "^4.2.19",
"ts-jest": "29.2.5",
"typescript": "5.6.3"
},
"peerDependencies": {
"@vue/compiler-sfc": "3.x",
"prettier": "2.x - 3.x"
"prettier": "2.x - 3.x",
"prettier-plugin-svelte": "3.x",
"svelte": "4.x"
},
"engines": {
"node": ">18.12"
},
"peerDependenciesMeta": {
"@vue/compiler-sfc": {
"optional": true
},
"prettier-plugin-svelte": {
"optional": true
},
"svelte": {
"optional": true
}
},
"resolutions": {
"@types/babel__generator": "7.6.8"
}
}
6 changes: 6 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,18 @@ export const newLineCharacters = '\n\n';

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

export const chunkSideEffectNode = 'side-effect-node';
export const chunkSideOtherNode = 'other-node';

/*
* Used to mark the position between RegExps,
* where the not matched imports should be placed
*/
export const THIRD_PARTY_MODULES_SPECIAL_WORD = '<THIRD_PARTY_MODULES>';

export const THIRD_PARTY_TYPES_SPECIAL_WORD = '<THIRD_PARTY_TS_TYPES>';
export const TYPES_SPECIAL_WORD = '<TS_TYPES>';

const PRETTIER_PLUGIN_SORT_IMPORTS_NEW_LINE =
'PRETTIER_PLUGIN_SORT_IMPORTS_NEW_LINE';

Expand Down
27 changes: 23 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { parsers as babelParsers } from 'prettier/parser-babel';
import { parsers as flowParsers } from 'prettier/parser-flow';
import { parsers as htmlParsers } from 'prettier/parser-html';
import { parsers as typescriptParsers } from 'prettier/parser-typescript';
import { parsers as babelParsers } from 'prettier/plugins/babel';
import { parsers as flowParsers } from 'prettier/plugins/flow';
import { parsers as htmlParsers } from 'prettier/plugins/html';
import { parsers as typescriptParsers } from 'prettier/plugins/typescript';

import { defaultPreprocessor } from './preprocessors/default-processor';
import { sveltePreprocessor } from './preprocessors/svelte-preprocessor';
import { vuePreprocessor } from './preprocessors/vue-preprocessor';

const { parsers: svelteParsers } = require('prettier-plugin-svelte');

const options = {
importOrder: {
type: 'path',
Expand Down Expand Up @@ -48,6 +51,18 @@ const options = {
default: false,
description: 'Should specifiers be sorted?',
},
importOrderSideEffects: {
type: 'boolean',
category: 'Global',
default: true,
description: 'Should side effects be sorted?',
},
importOrderImportAttributesKeyword: {
type: 'string',
category: 'Global',
default: 'with',
description: 'Provide a keyword for import attributes',
}
};

module.exports = {
Expand All @@ -68,6 +83,10 @@ module.exports = {
...htmlParsers.vue,
preprocess: vuePreprocessor,
},
svelte: {
...svelteParsers.svelte,
preprocess: sveltePreprocessor,
},
},
options,
};
4 changes: 3 additions & 1 deletion src/preprocessors/default-processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { PrettierOptions } from '../types';
import { preprocessor } from './preprocessor';

export function defaultPreprocessor(code: string, options: PrettierOptions) {
if (options.filepath?.endsWith('.vue')) return code;
for (const extension of ['svelte', 'vue']) {
if (options.filepath?.endsWith(`.${extension}`)) return code;
}
return preprocessor(code, options);
}
7 changes: 6 additions & 1 deletion src/preprocessors/preprocessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export function preprocessor(code: string, options: PrettierOptions) {
importOrderSeparation,
importOrderGroupNamespaceSpecifiers,
importOrderSortSpecifiers,
importOrderSideEffects,
importOrderImportAttributesKeyword,
} = options;

const parserOptions: ParserOptions = {
Expand All @@ -42,7 +44,10 @@ export function preprocessor(code: string, options: PrettierOptions) {
importOrderSeparation,
importOrderGroupNamespaceSpecifiers,
importOrderSortSpecifiers,
importOrderSideEffects,
});

return getCodeFromAst(allImports, directives, code, interpreter);
return getCodeFromAst(allImports, directives, code, interpreter, {
importOrderImportAttributesKeyword,
});
}
24 changes: 24 additions & 0 deletions src/preprocessors/svelte-preprocessor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { PrettierOptions } from '../types';
import { preprocessor } from './preprocessor';

const booleanGuard = <T>(value: T | undefined): value is T => Boolean(value);

const sortImports = (code: string, options: PrettierOptions) => {
const { parse } = require('svelte/compiler');
const { instance, module } = parse(code);
const sources = [instance, module].filter(booleanGuard);
if (!sources.length) return code;
return sources.reduce((code, source) => {
const snippet = code.slice(source.content.start, source.content.end);
const preprocessed = preprocessor(snippet, options);
const result = code.replace(snippet, `\n${preprocessed}\n`);
return result;
}, code);
};

export function sveltePreprocessor(code: string, options: PrettierOptions) {
const sorted = sortImports(code, options);

const prettierPluginSvelte = require('prettier-plugin-svelte');
return prettierPluginSvelte.parsers.svelte.preprocess(sorted, options);
}
27 changes: 24 additions & 3 deletions src/preprocessors/vue-preprocessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,31 @@ export function vuePreprocessor(code: string, options: PrettierOptions) {
const { parse } = require('@vue/compiler-sfc');
const { descriptor } = parse(code);

const content = (descriptor.script ?? descriptor.scriptSetup)?.content;
if (!content) {
const scriptContent = descriptor.script?.content;
const scriptSetupContent = descriptor.scriptSetup?.content;

if (!scriptContent && !scriptSetupContent) {
return code;
}

return code.replace(content, `\n${preprocessor(content, options)}\n`);
let transformedCode = code;

const replacer = (content: string) => {
// we pass the second argument as a function to avoid issues with the replacement string
// if string contained special groups (like $&, $`, $', $n, $<n>, etc.) this would produce invalid results
return transformedCode.replace(
content,
() => `\n${preprocessor(content, options)}\n`,
);
};

if (scriptContent) {
transformedCode = replacer(scriptContent);
}

if (scriptSetupContent) {
transformedCode = replacer(scriptSetupContent);
}

return transformedCode;
}
6 changes: 6 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,11 @@ export type GetSortedNodes = (
| 'importOrderSeparation'
| 'importOrderGroupNamespaceSpecifiers'
| 'importOrderSortSpecifiers'
| 'importOrderSideEffects'
>,
) => ImportOrLine[];

export interface ImportChunk {
nodes: ImportDeclaration[];
type: string;
}
Loading
Loading