diff --git a/README.md b/README.md index 8d664c4..d352520 100644 --- a/README.md +++ b/README.md @@ -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) @@ -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", "", "^[.]" ] + } + } +] +``` + #### `importOrderTypeScriptVersion` **type**: `string` diff --git a/src/constants.ts b/src/constants.ts index 3ce945c..94324ba 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -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 +]; diff --git a/src/index.ts b/src/index.ts index dfaeea2..dbd5cff 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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'; @@ -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]', }, diff --git a/src/preprocessors/preprocessor.ts b/src/preprocessors/preprocessor.ts index 6c54fe9..eb1622d 100644 --- a/src/preprocessors/preprocessor.ts +++ b/src/preprocessors/preprocessor.ts @@ -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, 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 f975b87..514a070 100644 --- a/src/utils/__tests__/get-all-comments-from-nodes.spec.ts +++ b/src/utils/__tests__/get-all-comments-from-nodes.spec.ts @@ -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'; @@ -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, }); }; diff --git a/src/utils/__tests__/get-code-from-ast.spec.ts b/src/utils/__tests__/get-code-from-ast.spec.ts index ed95925..db1303b 100644 --- a/src/utils/__tests__/get-code-from-ast.spec.ts +++ b/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'; @@ -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({ @@ -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({ diff --git a/src/utils/__tests__/get-sorted-nodes.spec.ts b/src/utils/__tests__/get-sorted-nodes.spec.ts index f68d5bd..081c3b6 100644 --- a/src/utils/__tests__/get-sorted-nodes.spec.ts +++ b/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'; @@ -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([ diff --git a/src/utils/__tests__/normalize-plugin-options.spec.ts b/src/utils/__tests__/normalize-plugin-options.spec.ts index 27299b9..ecdd1df 100644 --- a/src/utils/__tests__/normalize-plugin-options.spec.ts +++ b/src/utils/__tests__/normalize-plugin-options.spec.ts @@ -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'; @@ -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, @@ -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, @@ -103,6 +104,7 @@ describe('examineAndNormalizePluginOptions', () => { importOrder: [ BUILTIN_MODULES_REGEX_STR, THIRD_PARTY_MODULES_SPECIAL_WORD, + '^[.]', ], importOrderCombineTypeAndValueImports: true, plugins: [], @@ -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, @@ -168,6 +170,7 @@ describe('examineAndNormalizePluginOptions', () => { importOrder: [ BUILTIN_MODULES_REGEX_STR, THIRD_PARTY_MODULES_SPECIAL_WORD, + '^[.]', ], importOrderCombineTypeAndValueImports: true, plugins: ['typescript'], @@ -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, @@ -188,6 +191,7 @@ describe('examineAndNormalizePluginOptions', () => { importOrder: [ BUILTIN_MODULES_REGEX_STR, THIRD_PARTY_MODULES_SPECIAL_WORD, + '^[.]', ], importOrderCombineTypeAndValueImports: true, plugins: ['typescript'], @@ -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, @@ -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, + }); + }); }); 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 736fc35..8f39332 100644 --- a/src/utils/__tests__/remove-nodes-from-original-code.spec.ts +++ b/src/utils/__tests__/remove-nodes-from-original-code.spec.ts @@ -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'; @@ -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); diff --git a/src/utils/normalize-plugin-options.ts b/src/utils/normalize-plugin-options.ts index 5c3a27d..c114424 100644 --- a/src/utils/normalize-plugin-options.ts +++ b/src/utils/normalize-plugin-options.ts @@ -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. diff --git a/tests/Disabled/__snapshots__/ppsi.spec.ts.snap b/tests/Disabled/__snapshots__/ppsi.spec.ts.snap new file mode 100644 index 0000000..15b15cd --- /dev/null +++ b/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"; + +`; diff --git a/tests/Disabled/mess.ts b/tests/Disabled/mess.ts new file mode 100644 index 0000000..b69746a --- /dev/null +++ b/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"; diff --git a/tests/Disabled/ppsi.spec.ts b/tests/Disabled/ppsi.spec.ts new file mode 100644 index 0000000..cca1f2e --- /dev/null +++ b/tests/Disabled/ppsi.spec.ts @@ -0,0 +1,5 @@ +import {run_spec} from '../../test-setup/run_spec'; + +run_spec(__dirname, ["typescript"], { + importOrder: [], +}); diff --git a/types/index.d.ts b/types/index.d.ts index 56735b5..87eda0b 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -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:_ `[""", "", "^[.]"]` * - * 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. + * + * `` 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,