Skip to content

Commit

Permalink
Allow disabling plugin by setting importOrder to [] (#161)
Browse files Browse the repository at this point in the history
Closes #159

This changes up how we handle `importOrder: []` in the prettier config.
Previously, we would have applied our default config, such that
builtins, node modules, and relative imports would be sorted. Now, it
effectively disables the plugin so that no sorting takes place.

This could be seen as a breaking change, but instead I see it as a bug
fix. It doesn't make sense that using an explicit empty array for the
setting causes a fallback to be used.

Additionally, this gives users more control over disabling the plugin
and turning it on only for certain subfolders or types of files, as
shown in the referenced issue and documented in the readme in this PR.
  • Loading branch information
IanVS committed Mar 17, 2024
1 parent fe8ff2f commit 0ffe11e
Show file tree
Hide file tree
Showing 14 changed files with 123 additions and 30 deletions.
19 changes: 19 additions & 0 deletions README.md
Expand Up @@ -31,6 +31,7 @@ This project is based on [@trivago/prettier-plugin-sort-imports](https://github.
- [4. Group type imports separately from values](#4-group-type-imports-separately-from-values)
- [5. Group aliases with local imports](#5-group-aliases-with-local-imports)
- [6. Enforce a blank line after top of file comments](#6-enforce-a-blank-line-after-top-of-file-comments)
- [7. Enforce sort order only in certain folders or files](#7-enforce-sort-order-only-in-certain-folders-or-files)
- [`importOrderTypeScriptVersion`](#importordertypescriptversion)
- [`importOrderParserPlugins`](#importorderparserplugins)
- [Prevent imports from being sorted](#prevent-imports-from-being-sorted)
Expand Down Expand Up @@ -332,6 +333,24 @@ import icon from '@assets/icon';
import App from './App';
```

##### 7. Enforce sort order only in certain folders or files

If you'd like to sort the imports only in a specific set of files or directories, you can disable the plugin by setting `importOrder` to an empty array, and then use Prettier's [Configuration Overrides](https://prettier.io/docs/en/configuration#configuration-overrides) to set the order for files matching a glob pattern.

This can also be beneficial for large projects wishing to gradually adopt a sort order in a less disruptive approach than a single big-bang change.

```json
"importOrder": []
"overrides": [
{
"files": "**/*.test.ts",
"options": {
"importOrder": [ "^vitest", "<THIRD_PARTY_MODULES>", "^[.]" ]
}
}
]
```

#### `importOrderTypeScriptVersion`

**type**: `string`
Expand Down
7 changes: 7 additions & 0 deletions src/constants.ts
Expand Up @@ -54,3 +54,10 @@ export const forceANewlineUsingACommentStatement = () => ({

export const injectNewlinesRegex =
/("PRETTIER_PLUGIN_SORT_IMPORTS_NEW_LINE";|\/\/PRETTIER_PLUGIN_SORT_IMPORTS_NEWLINE_COMMENT)/gi;

// This default is set by prettier itself by including it in our config in index.ts
export const DEFAULT_IMPORT_ORDER = [
BUILTIN_MODULES_SPECIAL_WORD,
THIRD_PARTY_MODULES_SPECIAL_WORD, // Everything not matching relative imports
'^[.]', // relative imports
];
11 changes: 2 additions & 9 deletions src/index.ts
Expand Up @@ -6,6 +6,7 @@ import { parsers as typescriptParsers } from 'prettier/parser-typescript';

import {
BUILTIN_MODULES_SPECIAL_WORD,
DEFAULT_IMPORT_ORDER,
THIRD_PARTY_MODULES_SPECIAL_WORD,
} from './constants';
import { defaultPreprocessor } from './preprocessors/default';
Expand All @@ -29,15 +30,7 @@ export const options: Record<
type: 'path',
category: 'Global',
array: true,
default: [
{
value: [
BUILTIN_MODULES_SPECIAL_WORD,
THIRD_PARTY_MODULES_SPECIAL_WORD, // Everything not matching relative imports
'^[.]', // relative imports
],
},
],
default: [{ value: DEFAULT_IMPORT_ORDER }],
description:
'Provide an order to sort imports. [node.js built-ins are always first]',
},
Expand Down
5 changes: 5 additions & 0 deletions src/preprocessors/preprocessor.ts
Expand Up @@ -39,6 +39,11 @@ export function preprocessor(code: string, options: PrettierOptions): string {
return code;
}

// short-circuit if importOrder is an empty array (can be used to disable plugin)
if (!remainingOptions.importOrder.length) {
return code;
}

const nodesToOutput = getSortedNodes(
allOriginalImportNodes,
remainingOptions,
Expand Down
4 changes: 3 additions & 1 deletion src/utils/__tests__/get-all-comments-from-nodes.spec.ts
Expand Up @@ -6,6 +6,7 @@ import type {
} from '@babel/types';
import { expect, test } from 'vitest';

import { DEFAULT_IMPORT_ORDER } from '../../constants';
import { getAllCommentsFromNodes } from '../get-all-comments-from-nodes';
import { getImportNodes } from '../get-import-nodes';
import { getSortedNodes } from '../get-sorted-nodes';
Expand All @@ -15,7 +16,8 @@ const getSortedImportNodes = (code: string, options?: ParserOptions) => {
const importNodes: ImportDeclaration[] = getImportNodes(code, options);

return getSortedNodes(importNodes, {
importOrder: testingOnly.normalizeImportOrderOption([]),
importOrder:
testingOnly.normalizeImportOrderOption(DEFAULT_IMPORT_ORDER),
importOrderCombineTypeAndValueImports: true,
});
};
Expand Down
8 changes: 5 additions & 3 deletions src/utils/__tests__/get-code-from-ast.spec.ts
@@ -1,12 +1,14 @@
import { format } from 'prettier';
import { expect, test } from 'vitest';

import { DEFAULT_IMPORT_ORDER } from '../../constants';
import { getCodeFromAst } from '../get-code-from-ast';
import { getImportNodes } from '../get-import-nodes';
import { getSortedNodes } from '../get-sorted-nodes';
import { testingOnly } from '../normalize-plugin-options';

const emptyImportOrder = testingOnly.normalizeImportOrderOption([]);
const defaultImportOrder =
testingOnly.normalizeImportOrderOption(DEFAULT_IMPORT_ORDER);

test('sorts imports correctly', async () => {
const code = `import z from 'z';
Expand All @@ -18,7 +20,7 @@ import a from 'a';
`;
const importNodes = getImportNodes(code);
const sortedNodes = getSortedNodes(importNodes, {
importOrder: emptyImportOrder,
importOrder: defaultImportOrder,
importOrderCombineTypeAndValueImports: true,
});
const formatted = getCodeFromAst({
Expand Down Expand Up @@ -50,7 +52,7 @@ import type {See} from 'c';
`;
const importNodes = getImportNodes(code, { plugins: ['typescript'] });
const sortedNodes = getSortedNodes(importNodes, {
importOrder: emptyImportOrder,
importOrder: defaultImportOrder,
importOrderCombineTypeAndValueImports: true,
});
const formatted = getCodeFromAst({
Expand Down
4 changes: 3 additions & 1 deletion src/utils/__tests__/get-sorted-nodes.spec.ts
@@ -1,6 +1,7 @@
import type { ImportDeclaration } from '@babel/types';
import { expect, test } from 'vitest';

import { DEFAULT_IMPORT_ORDER } from '../../constants';
import { getImportNodes } from '../get-import-nodes';
import { getSortedNodes } from '../get-sorted-nodes';
import { getSortedNodesModulesNames } from '../get-sorted-nodes-modules-names';
Expand Down Expand Up @@ -29,7 +30,8 @@ import "se2";
test('it returns all sorted nodes, preserving the order side effect nodes', () => {
const result = getImportNodes(code);
const sorted = getSortedNodes(result, {
importOrder: testingOnly.normalizeImportOrderOption([]),
importOrder:
testingOnly.normalizeImportOrderOption(DEFAULT_IMPORT_ORDER),
importOrderCombineTypeAndValueImports: true,
}) as ImportDeclaration[];
expect(getSortedNodesNamesAndNewlines(sorted)).toEqual([
Expand Down
38 changes: 30 additions & 8 deletions src/utils/__tests__/normalize-plugin-options.spec.ts
Expand Up @@ -3,6 +3,7 @@ import { describe, expect, test } from 'vitest';
import {
BUILTIN_MODULES_REGEX_STR,
BUILTIN_MODULES_SPECIAL_WORD,
DEFAULT_IMPORT_ORDER,
THIRD_PARTY_MODULES_SPECIAL_WORD,
} from '../../constants';
import { NormalizableOptions } from '../../types';
Expand All @@ -12,11 +13,11 @@ import {
} from '../normalize-plugin-options';

describe('normalizeImportOrderOption', () => {
test('it should not inject defaults if [] is passed explicitly', () => {
expect(testingOnly.normalizeImportOrderOption([])).toEqual([]);
});

test('it should inject required modules if not present', () => {
expect(testingOnly.normalizeImportOrderOption([])).toEqual([
BUILTIN_MODULES_REGEX_STR,
THIRD_PARTY_MODULES_SPECIAL_WORD,
]);
expect(testingOnly.normalizeImportOrderOption(['^[.]'])).toEqual([
BUILTIN_MODULES_REGEX_STR,
THIRD_PARTY_MODULES_SPECIAL_WORD,
Expand Down Expand Up @@ -93,7 +94,7 @@ describe('examineAndNormalizePluginOptions', () => {
test('it should set most defaults', () => {
expect(
examineAndNormalizePluginOptions({
importOrder: [],
importOrder: DEFAULT_IMPORT_ORDER,
importOrderParserPlugins: [],
importOrderTypeScriptVersion: '1.0.0',
filepath: __filename,
Expand All @@ -103,6 +104,7 @@ describe('examineAndNormalizePluginOptions', () => {
importOrder: [
BUILTIN_MODULES_REGEX_STR,
THIRD_PARTY_MODULES_SPECIAL_WORD,
'^[.]',
],
importOrderCombineTypeAndValueImports: true,
plugins: [],
Expand Down Expand Up @@ -158,7 +160,7 @@ describe('examineAndNormalizePluginOptions', () => {
test('it should detect typescript-version-dependent-flags', () => {
expect(
examineAndNormalizePluginOptions({
importOrder: [],
importOrder: DEFAULT_IMPORT_ORDER,
importOrderParserPlugins: ['typescript'],
importOrderTypeScriptVersion: '5.0.0',
filepath: __filename,
Expand All @@ -168,6 +170,7 @@ describe('examineAndNormalizePluginOptions', () => {
importOrder: [
BUILTIN_MODULES_REGEX_STR,
THIRD_PARTY_MODULES_SPECIAL_WORD,
'^[.]',
],
importOrderCombineTypeAndValueImports: true,
plugins: ['typescript'],
Expand All @@ -178,7 +181,7 @@ describe('examineAndNormalizePluginOptions', () => {
// full tests for getExperimentalParserPlugins is in its own spec file
expect(
examineAndNormalizePluginOptions({
importOrder: [],
importOrder: DEFAULT_IMPORT_ORDER,
importOrderParserPlugins: ['typescript', 'jsx'],
importOrderTypeScriptVersion: '5.0.0',
filepath: __filename,
Expand All @@ -188,6 +191,7 @@ describe('examineAndNormalizePluginOptions', () => {
importOrder: [
BUILTIN_MODULES_REGEX_STR,
THIRD_PARTY_MODULES_SPECIAL_WORD,
'^[.]',
],
importOrderCombineTypeAndValueImports: true,
plugins: ['typescript'],
Expand All @@ -197,7 +201,7 @@ describe('examineAndNormalizePluginOptions', () => {
test('it should not have a problem with a missing filepath', () => {
expect(
examineAndNormalizePluginOptions({
importOrder: [],
importOrder: DEFAULT_IMPORT_ORDER,
importOrderParserPlugins: [],
importOrderTypeScriptVersion: '1.0.0',
filepath: undefined,
Expand All @@ -207,10 +211,28 @@ describe('examineAndNormalizePluginOptions', () => {
importOrder: [
BUILTIN_MODULES_REGEX_STR,
THIRD_PARTY_MODULES_SPECIAL_WORD,
'^[.]',
],
importOrderCombineTypeAndValueImports: true,
plugins: [],
provideGapAfterTopOfFileComments: false,
});
});

test('it should be disabled if importOrder is empty array', () => {
expect(
examineAndNormalizePluginOptions({
importOrder: [],
importOrderParserPlugins: [],
importOrderTypeScriptVersion: '1.0.0',
filepath: __filename,
} as NormalizableOptions),
).toEqual({
hasAnyCustomGroupSeparatorsInImportOrder: false,
importOrder: [],
importOrderCombineTypeAndValueImports: true,
plugins: [],
provideGapAfterTopOfFileComments: false,
});
});
});
4 changes: 3 additions & 1 deletion src/utils/__tests__/remove-nodes-from-original-code.spec.ts
Expand Up @@ -2,6 +2,7 @@ import { parse as babelParser } from '@babel/parser';
import { format } from 'prettier';
import { expect, test } from 'vitest';

import { DEFAULT_IMPORT_ORDER } from '../../constants';
import { getAllCommentsFromNodes } from '../get-all-comments-from-nodes';
import { getImportNodes } from '../get-import-nodes';
import { getSortedNodes } from '../get-sorted-nodes';
Expand All @@ -24,7 +25,8 @@ test('it should remove nodes from the original code', async () => {
const ast = babelParser(code, { sourceType: 'module' });
const importNodes = getImportNodes(code);
const sortedNodes = getSortedNodes(importNodes, {
importOrder: testingOnly.normalizeImportOrderOption([]),
importOrder:
testingOnly.normalizeImportOrderOption(DEFAULT_IMPORT_ORDER),
importOrderCombineTypeAndValueImports: true,
});
const allCommentsFromImports = getAllCommentsFromNodes(sortedNodes);
Expand Down
9 changes: 7 additions & 2 deletions src/utils/normalize-plugin-options.ts
Expand Up @@ -9,12 +9,17 @@ import {
import { ExtendedOptions, NormalizableOptions } from '../types';
import { getExperimentalParserPlugins } from './get-experimental-parser-plugins';

// If importOrder is not set in the config, it will be pre-populated with the default before it hits this
// function. This means we should never get something like `undefined`, and if we see a config of `[]`,
// someone set that explicitly in their config.
function normalizeImportOrderOption(
importOrder: NormalizableOptions['importOrder'],
) {
if (importOrder == null) {
importOrder = [];
// Allow disabling by setting an empty array, short-circuit
if (Array.isArray(importOrder) && !importOrder.length) {
return importOrder;
}

importOrder = [...importOrder]; // Clone the array so we can splice it

// If we have a separator in the first slot, we need to inject our required words after it.
Expand Down
20 changes: 20 additions & 0 deletions tests/Disabled/__snapshots__/ppsi.spec.ts.snap
@@ -0,0 +1,20 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`mess.ts - typescript-verify > mess.ts 1`] = `
import z from 'z';
import { isEmpty } from "lodash-es";
import threeLevelRelativePath from "../../../threeLevelRelativePath";
import sameLevelRelativePath from "./sameLevelRelativePath";
import thirdParty from "third-party";
import oneLevelRelativePath from "../oneLevelRelativePath";
import path from "path";
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
import z from "z";
import { isEmpty } from "lodash-es";
import threeLevelRelativePath from "../../../threeLevelRelativePath";
import sameLevelRelativePath from "./sameLevelRelativePath";
import thirdParty from "third-party";
import oneLevelRelativePath from "../oneLevelRelativePath";
import path from "path";
`;
7 changes: 7 additions & 0 deletions tests/Disabled/mess.ts
@@ -0,0 +1,7 @@
import z from 'z';
import { isEmpty } from "lodash-es";
import threeLevelRelativePath from "../../../threeLevelRelativePath";
import sameLevelRelativePath from "./sameLevelRelativePath";
import thirdParty from "third-party";
import oneLevelRelativePath from "../oneLevelRelativePath";
import path from "path";
5 changes: 5 additions & 0 deletions tests/Disabled/ppsi.spec.ts
@@ -0,0 +1,5 @@
import {run_spec} from '../../test-setup/run_spec';

run_spec(__dirname, ["typescript"], {
importOrder: [],
});
12 changes: 7 additions & 5 deletions types/index.d.ts
Expand Up @@ -10,14 +10,16 @@ export interface PluginConfig {
* A collection of Regular expressions in string format.
*
* ```json
* "importOrder": ["^@core/(.*)$", "^@server/(.*)$", "^@ui/(.*)$", "^[./]"],
* "importOrder": ["^@core/(.*)$", "^@server/(.*)$", "^@ui/(.*)$", "^[.]"],
* ```
*
* _Default:_ `[]`
* _Default:_ `["<BUILTIN_MODULES>"", "<THIRD_PARTY_MODULES>", "^[.]"]`
*
* By default, this plugin will not move any imports.
* To separate third party from relative imports, use `["^[./]"]`.
* This will become the default in the next major version.
* By default, this plugin will sort node.js built-in modules to the top, followed by non-relative
* imports (usually third-party modules), and finally relative imports.
*
* `<THIRD_PARTY_MODULES>` is a special value that will match any imports not matched by any other regex patterns.
* We'll call them "third party imports" for simplicity, since that's what they usually are.
*
* 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,
Expand Down

0 comments on commit 0ffe11e

Please sign in to comment.