diff --git a/README.md b/README.md index 94b615a..a135c4f 100644 --- a/README.md +++ b/README.md @@ -135,6 +135,13 @@ import-conductor -p @customA @customB import-conductor --separator '' ==> no separator ``` +- `groupOrder` - The group order to follow: (defaults to `[thirdParty, userLibrary, differentModule, sameModule]`) + +```shell script +import-conductor --groupOrder 'userLibrary' 'differentModule' 'sameModule' 'thirdParty' +import-conductor -g 'userLibrary' 'differentModule' 'sameModule' 'thirdParty' +``` + - `staged` - Run against staged files: (defaults to `false`) ```shell script diff --git a/__tests__/optimize-imports-mocks.ts b/__tests__/optimize-imports-mocks.ts index 9453a0d..b148831 100644 --- a/__tests__/optimize-imports-mocks.ts +++ b/__tests__/optimize-imports-mocks.ts @@ -1,4 +1,4 @@ -export type TestCase = { input: string; expected: string; noOfRuns?: number }; +export type TestCase = { input: string; expected: string; noOfRuns?: number; groupOrder?: string[] }; export const readmeExample: TestCase = { input: `import fs from 'fs'; @@ -111,3 +111,43 @@ import { MatDialogRef } from '@angular/material/dialog'; import { Observable } from 'rxjs'; import { AboutDialogBloc, AboutState } from './about-dialog.bloc';`, }; + +export const noImportStatement: TestCase = { + input: `const x = 2;`, + expected: `const x = 2;`, +}; + +export const importsOnDifferentGroupOrder: TestCase = { + groupOrder: ['userLibrary', 'sameModule', 'differentModule', 'thirdParty'], + input: `import { Component } from '@angular/core'; +import fs from 'fs'; +import { LoggerService } from '@myorg/logger'; +import { Order } from '../order/order.model'; +import { CustomService } from './customer.service';`, + expected: `import { LoggerService } from '@myorg/logger'; +import { CustomService } from './customer.service'; +import { Order } from '../order/order.model'; +import { Component } from '@angular/core'; +import fs from 'fs';`, +}; + +export const importsWithGroupOrderIncorrect: TestCase = { + groupOrder: ['userLibrary', 'differentModule', 'thirdParty'], + input: `import fs from 'fs'; +import { CustomerService } from './customer.service'; +import { Order } from '../order/order.model'; +import { Component, OnInit } from '@angular/core'; +import { LoggerService } from '@myorg/logger'; +import { Observable } from 'rxjs'; +import { spawn } from 'child_process';`, + expected: `import { Component, OnInit } from '@angular/core'; +import { spawn } from 'child_process'; +import fs from 'fs'; +import { Observable } from 'rxjs'; + +import { LoggerService } from '@myorg/logger'; + +import { Order } from '../order/order.model'; + +import { CustomerService } from './customer.service';`, +}; diff --git a/__tests__/optimize-imports.spec.ts b/__tests__/optimize-imports.spec.ts index ee70223..ae1086c 100644 --- a/__tests__/optimize-imports.spec.ts +++ b/__tests__/optimize-imports.spec.ts @@ -2,8 +2,18 @@ import { actions, organizeImports, organizeImportsForFile } from '@ic/conductor/ import * as config from '@ic/config'; import fs from 'fs'; import { Config } from '@ic/types'; -import { readmeExample, comments, TestCase, codeBetweenImports, emptyNewLineSeparator } from './optimize-imports-mocks'; +import { + readmeExample, + comments, + TestCase, + codeBetweenImports, + emptyNewLineSeparator, + noImportStatement, + importsOnDifferentGroupOrder, + importsWithGroupOrderIncorrect, +} from './optimize-imports-mocks'; import { defaultConfig } from '@ic/defaultConfig'; +import { getGroupOrder } from '@ic/conductor/get-group-order'; jest.mock('fs'); jest.mock('simple-git'); @@ -72,4 +82,22 @@ describe('optimizeImports', () => { expect(fs.writeFileSync).not.toHaveBeenCalled(); } }); + + it('should do nothing if the file has no import', async () => { + (fs.readFileSync as any).mockReturnValue(Buffer.from(noImportStatement.input)); + const file = 'test.ts'; + const result = await organizeImportsForFile(file); + expect(result).toBe(actions.none); + expect(fs.writeFileSync).not.toHaveBeenCalled(); + }); + + it('should change group order', async () => { + spy.and.returnValue({ ...basicConfig, groupOrder: importsOnDifferentGroupOrder.groupOrder, separator: '' }); + await assertConductor(importsOnDifferentGroupOrder); + }); + + it('should use default order because incorrect group order input', async () => { + spy.and.returnValue({ ...basicConfig, groupOrder: getGroupOrder({ groupOrder: importsWithGroupOrderIncorrect.groupOrder }) }); + await assertConductor(importsWithGroupOrderIncorrect); + }); }); diff --git a/src/cliOptions.ts b/src/cliOptions.ts index abb7322..d4b2a0c 100644 --- a/src/cliOptions.ts +++ b/src/cliOptions.ts @@ -25,6 +25,13 @@ export const optionDefinitions = [ multiple: true, description: 'The prefix of custom user libraries', }, + { + name: 'groupOrder', + alias: 'g', + type: String, + multiple: true, + description: 'The group order it should follow', + }, { name: 'staged', type: Boolean, diff --git a/src/conductor/format-import-statements.ts b/src/conductor/format-import-statements.ts index a230447..ded120a 100644 --- a/src/conductor/format-import-statements.ts +++ b/src/conductor/format-import-statements.ts @@ -3,13 +3,11 @@ import { ImportCategories } from '../types'; type CategoryEntry = [string, Map]; -const categoriesOrder = ['thirdParty', 'userLibrary', 'differentModule', 'sameModule']; - export function formatImportStatements(importCategories: ImportCategories, lineEnding: string) { const { separator } = getConfig(); const [first, ...otherCategories] = Object.entries(importCategories) .filter(hasImports) - .sort(byCategoriesOrder) + .sort(byCategoriesOrder(getConfig().groupOrder)) .map((imports) => toImportBlock(imports, lineEnding)); let result = first || ''; @@ -21,8 +19,8 @@ export function formatImportStatements(importCategories: ImportCategories, lineE return result; } -function byCategoriesOrder([a]: CategoryEntry, [b]: CategoryEntry): number { - return categoriesOrder.indexOf(a) - categoriesOrder.indexOf(b); +function byCategoriesOrder(categoriesOrder: string[]) { + return ([a]: CategoryEntry, [b]: CategoryEntry): number => categoriesOrder.indexOf(a) - categoriesOrder.indexOf(b); } function hasImports([, imports]: CategoryEntry) { diff --git a/src/conductor/get-group-order.ts b/src/conductor/get-group-order.ts new file mode 100644 index 0000000..7fe64f6 --- /dev/null +++ b/src/conductor/get-group-order.ts @@ -0,0 +1,12 @@ +import { defaultConfig } from '../defaultConfig'; +import { Config } from '../types'; + +export function getGroupOrder(config: Partial) { + const groups = new Set(config?.groupOrder || []); + const uniqueGroups = Array.from(groups); + return isValidGroupArgument(uniqueGroups) ? uniqueGroups : defaultConfig.groupOrder; +} + +function isValidGroupArgument(groups: string[]): boolean { + return groups.length === defaultConfig.groupOrder.length && groups.every((group) => defaultConfig.groupOrder.includes(group)); +} diff --git a/src/conductor/organize-imports.ts b/src/conductor/organize-imports.ts index 4a24421..4577926 100644 --- a/src/conductor/organize-imports.ts +++ b/src/conductor/organize-imports.ts @@ -45,7 +45,7 @@ export async function organizeImportsForFile(filePath: string): Promise const { staged, autoAdd, dryRun } = getConfig(); const fileWithOrganizedImports = await organizeImports(fileContent); const fileHasChanged = fileWithOrganizedImports !== fileContent; - const isValidAction = [actions.none, actions.skipped].every(action => action !== fileWithOrganizedImports); + const isValidAction = [actions.none, actions.skipped].every((action) => action !== fileWithOrganizedImports); if (fileHasChanged && isValidAction) { !dryRun && writeFileSync(filePath, fileWithOrganizedImports); @@ -55,11 +55,11 @@ export async function organizeImportsForFile(filePath: string): Promise msg += ', added to git'; } log('green', msg, filePath); - } else { - log('gray', 'no change needed', filePath); + return actions.reordered; } - return fileHasChanged ? actions.reordered : actions.none; + log('gray', 'no change needed', filePath); + return actions.none; } export async function organizeImports(fileContent: string): Promise { diff --git a/src/config.ts b/src/config.ts index 805fbc4..5c3c2ee 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,5 +1,6 @@ import { sync } from 'glob'; +import { getGroupOrder } from './conductor/get-group-order'; import { getThirdParty } from './conductor/get-third-party'; import { defaultConfig } from './defaultConfig'; import { CliConfig, Config } from './types'; @@ -17,6 +18,7 @@ export function resolveConfig(cliConfig: Partial): Config { ...defaultConfig, ...normalized, thirdPartyDependencies: getThirdParty(), + groupOrder: getGroupOrder(normalized), }; if (merged.ignore.length > 0) { merged.ignore = merged.ignore.map((pattern) => (pattern.includes('*') ? sync(pattern) : pattern)).flat(); diff --git a/src/defaultConfig.ts b/src/defaultConfig.ts index 6287f36..68b9244 100644 --- a/src/defaultConfig.ts +++ b/src/defaultConfig.ts @@ -10,4 +10,5 @@ export const defaultConfig: Config = { staged: false, dryRun: false, ignore: [], + groupOrder: ['thirdParty', 'userLibrary', 'differentModule', 'sameModule'], }; diff --git a/src/types.ts b/src/types.ts index e8dedbb..301afc8 100644 --- a/src/types.ts +++ b/src/types.ts @@ -13,6 +13,7 @@ export interface Config { ignore: string[]; userLibPrefixes: string[]; thirdPartyDependencies?: Set; + groupOrder: string[]; } export type CliConfig = Omit & { noAutoAdd: boolean };