diff --git a/rules/sort-decorators.ts b/rules/sort-decorators.ts index 691498d1..8e851e4a 100644 --- a/rules/sort-decorators.ts +++ b/rules/sort-decorators.ts @@ -78,11 +78,11 @@ export default createEslintRule({ let options = complete(context.options.at(0), settings, defaultOptions) validateCustomSortConfiguration(options) - validateGroupsConfiguration( - options.groups, - ['unknown'], - Object.keys(options.customGroups), - ) + validateGroupsConfiguration({ + allowedCustomGroups: Object.keys(options.customGroups), + allowedPredefinedGroups: ['unknown'], + options, + }) return { Decorator: decorator => { diff --git a/rules/sort-heritage-clauses.ts b/rules/sort-heritage-clauses.ts index c9fbc81a..4d28e2d7 100644 --- a/rules/sort-heritage-clauses.ts +++ b/rules/sort-heritage-clauses.ts @@ -83,11 +83,11 @@ export default createEslintRule({ let options = complete(context.options.at(0), settings, defaultOptions) validateCustomSortConfiguration(options) - validateGroupsConfiguration( - options.groups, - ['unknown'], - Object.keys(options.customGroups), - ) + validateGroupsConfiguration({ + allowedCustomGroups: Object.keys(options.customGroups), + allowedPredefinedGroups: ['unknown'], + options, + }) return { TSInterfaceDeclaration: declaration => diff --git a/rules/sort-imports.ts b/rules/sort-imports.ts index a9864067..9b5f9c0f 100644 --- a/rules/sort-imports.ts +++ b/rules/sort-imports.ts @@ -135,9 +135,8 @@ export default createEslintRule({ } as const), ) - validateGroupsConfiguration( - options.groups, - [ + validateGroupsConfiguration({ + allowedPredefinedGroups: [ 'side-effect-style', 'external-type', 'internal-type', @@ -157,11 +156,12 @@ export default createEslintRule({ 'style', 'type', ], - [ + allowedCustomGroups: [ ...Object.keys(options.customGroups.type ?? {}), ...Object.keys(options.customGroups.value ?? {}), ], - ) + options, + }) validateCustomSortConfiguration(options) validateNewlinesAndPartitionConfiguration(options) diff --git a/rules/sort-jsx-props.ts b/rules/sort-jsx-props.ts index ed0957b2..5c64a132 100644 --- a/rules/sort-jsx-props.ts +++ b/rules/sort-jsx-props.ts @@ -84,11 +84,11 @@ export default createEslintRule({ let settings = getSettings(context.settings) let options = complete(context.options.at(0), settings, defaultOptions) validateCustomSortConfiguration(options) - validateGroupsConfiguration( - options.groups, - ['multiline', 'shorthand', 'unknown'], - Object.keys(options.customGroups), - ) + validateGroupsConfiguration({ + allowedPredefinedGroups: ['multiline', 'shorthand', 'unknown'], + allowedCustomGroups: Object.keys(options.customGroups), + options, + }) validateNewlinesAndPartitionConfiguration(options) let sourceCode = getSourceCode(context) diff --git a/rules/sort-union-types.ts b/rules/sort-union-types.ts index fa37160c..176893f8 100644 --- a/rules/sort-union-types.ts +++ b/rules/sort-union-types.ts @@ -155,9 +155,8 @@ export let sortUnionOrIntersectionTypes = ({ let options = complete(context.options.at(0), settings, defaultOptions) validateCustomSortConfiguration(options) - validateGroupsConfiguration( - options.groups, - [ + validateGroupsConfiguration({ + allowedPredefinedGroups: [ 'intersection', 'conditional', 'function', @@ -172,8 +171,9 @@ export let sortUnionOrIntersectionTypes = ({ 'tuple', 'union', ], - [], - ) + allowedCustomGroups: [], + options, + }) validateNewlinesAndPartitionConfiguration(options) let sourceCode = getSourceCode(context) diff --git a/test/utils/validate-generated-groups-configuration.test.ts b/test/utils/validate-generated-groups-configuration.test.ts index 4c752778..40d4d86c 100644 --- a/test/utils/validate-generated-groups-configuration.test.ts +++ b/test/utils/validate-generated-groups-configuration.test.ts @@ -87,6 +87,22 @@ describe('validate-generated-groups-configuration', () => { }), ).toThrow('Invalid group(s): myCustomGroup') }) + + it('throws an error with consecutive newlines objects', () => { + expect(() => { + validateGeneratedGroupsConfiguration({ + options: { + groups: [ + { newlinesBetween: 'always' }, + { newlinesBetween: 'always' }, + ], + customGroups: [], + }, + selectors: [], + modifiers: [], + }) + }).toThrow("Consecutive 'newlinesBetween' objects are not allowed") + }) }) let getAllNonEmptyCombinations = (array: string[]): string[][] => { diff --git a/test/utils/validate-groups-configuration.test.ts b/test/utils/validate-groups-configuration.test.ts index 50ffa477..dfa18081 100644 --- a/test/utils/validate-groups-configuration.test.ts +++ b/test/utils/validate-groups-configuration.test.ts @@ -14,36 +14,46 @@ import { validateGroupsConfiguration } from '../../utils/validate-groups-configu describe('validate-groups-configuration', () => { it('throws an error when an invalid group is provided', () => { expect(() => { - validateGroupsConfiguration( - ['predefinedGroup', ['customGroup', 'invalidGroup1'], 'invalidGroup2'], - ['predefinedGroup'], - ['customGroup'], - ) + validateGroupsConfiguration({ + options: { + groups: [ + 'predefinedGroup', + ['customGroup', 'invalidGroup1'], + 'invalidGroup2', + ], + }, + allowedPredefinedGroups: ['predefinedGroup'], + allowedCustomGroups: ['customGroup'], + }) }).toThrow('Invalid group(s): invalidGroup1, invalidGroup2') }) it('throws an error when a duplicate group is provided', () => { expect(() => { - validateGroupsConfiguration( - ['predefinedGroup', 'predefinedGroup'], - ['predefinedGroup'], - [], - ) + validateGroupsConfiguration({ + options: { + groups: ['predefinedGroup', 'predefinedGroup'], + }, + allowedPredefinedGroups: ['predefinedGroup'], + allowedCustomGroups: [], + }) }).toThrow('Duplicated group(s): predefinedGroup') }) it('throws an error with consecutive newlines objects', () => { expect(() => { - validateGroupsConfiguration( - [ - 'a', - { newlinesBetween: 'always' }, - { newlinesBetween: 'always' }, - 'b', - ], - ['a', 'b'], - [], - ) + validateGroupsConfiguration({ + options: { + groups: [ + 'a', + { newlinesBetween: 'always' }, + { newlinesBetween: 'always' }, + 'b', + ], + }, + allowedPredefinedGroups: ['a', 'b'], + allowedCustomGroups: [], + }) }).toThrow("Consecutive 'newlinesBetween' objects are not allowed") }) }) diff --git a/test/utils/validate-newlines-between-inside-groups.test.ts b/test/utils/validate-newlines-between-inside-groups.test.ts new file mode 100644 index 00000000..ee565154 --- /dev/null +++ b/test/utils/validate-newlines-between-inside-groups.test.ts @@ -0,0 +1,13 @@ +import { describe, expect, it } from 'vitest' + +import { validateNewlinesBetweenInsideGroups } from '../../utils/validate-newlines-between-inside-groups' + +describe('validate-newlines-between-inside-groups', () => { + it('throws an error with consecutive newlines objects', () => { + expect(() => { + validateNewlinesBetweenInsideGroups({ + groups: [{ newlinesBetween: 'always' }, { newlinesBetween: 'always' }], + }) + }).toThrow("Consecutive 'newlinesBetween' objects are not allowed") + }) +}) diff --git a/utils/validate-generated-groups-configuration.ts b/utils/validate-generated-groups-configuration.ts index 00c1a0ff..d7d349fb 100644 --- a/utils/validate-generated-groups-configuration.ts +++ b/utils/validate-generated-groups-configuration.ts @@ -4,7 +4,8 @@ import type { GroupsOptions, } from '../types/common-options' -import { validateNoDuplicatedGroups } from './validate-groups-configuration' +import { validateNewlinesBetweenInsideGroups } from './validate-newlines-between-inside-groups' +import { validateNoDuplicatedGroups } from './validate-no-duplicated-groups' interface ValidateGenerateGroupsConfigurationParameters { options: { @@ -36,7 +37,8 @@ export let validateGeneratedGroupsConfiguration = ({ if (invalidGroups.length > 0) { throw new Error(`Invalid group(s): ${invalidGroups.join(', ')}`) } - validateNoDuplicatedGroups(options.groups) + validateNoDuplicatedGroups(options) + validateNewlinesBetweenInsideGroups(options) } let isPredefinedGroup = ( diff --git a/utils/validate-groups-configuration.ts b/utils/validate-groups-configuration.ts index 2ed15491..81f10be1 100644 --- a/utils/validate-groups-configuration.ts +++ b/utils/validate-groups-configuration.ts @@ -1,79 +1,58 @@ -import type { NewlinesBetweenOption } from '../types/common-options' +import type { GroupsOptions } from '../types/common-options' +import { validateNewlinesBetweenInsideGroups } from './validate-newlines-between-inside-groups' +import { validateNoDuplicatedGroups } from './validate-no-duplicated-groups' import { isNewlinesBetweenOption } from './is-newlines-between-option' -type Group = { newlinesBetween: NewlinesBetweenOption } | string[] | string +interface ValidateGroupsConfigurationParameters { + options: { + groups: GroupsOptions + } + allowedPredefinedGroups: string[] + allowedCustomGroups: string[] +} /** * Throws an error if one of the following conditions is met: * - One or more groups specified in `groups` are not predefined nor specified * in `customGroups` * - A group is specified in `groups` more than once - * @param {Group[]} groups - The groups to validate. - * @param {string[]} allowedPredefinedGroups - An array of predefined group + * @param {object} parameters - Parameters object. + * @param {object} parameters.options - Options containing the groups to validate. + * @param {string[]} parameters.allowedPredefinedGroups - An array of predefined + * group names that are considered valid. + * @param {string[]} parameters.allowedCustomGroups - An array of custom group * names that are considered valid. - * @param {string[]} allowedCustomGroups - An array of custom group names that - * are considered valid. - * @throws Will throw an error if invalid or duplicated groups are found. + * @throws Error Will throw an error if invalid or duplicated groups are found. */ -export let validateGroupsConfiguration = ( - groups: Group[], - allowedPredefinedGroups: string[], - allowedCustomGroups: string[], -): void => { +export let validateGroupsConfiguration = ({ + allowedPredefinedGroups, + allowedCustomGroups, + options, +}: ValidateGroupsConfigurationParameters): void => { let allowedGroupsSet = new Set([ ...allowedPredefinedGroups, ...allowedCustomGroups, ]) let invalidGroups: string[] = [] - let isPreviousElementNewlinesBetween = false - for (let groupElement of groups) { + + for (let groupElement of options.groups) { if (isNewlinesBetweenOption(groupElement)) { - // There should not be two consecutive `newlinesBetween` objects - if (isPreviousElementNewlinesBetween) { - throw new Error("Consecutive 'newlinesBetween' objects are not allowed") - } - isPreviousElementNewlinesBetween = true - } else { - isPreviousElementNewlinesBetween = false - let groupElements = Array.isArray(groupElement) - ? groupElement - : [groupElement] - for (let group of groupElements) { - if (!allowedGroupsSet.has(group)) { - invalidGroups.push(group) - } + continue + } + let groupElements = Array.isArray(groupElement) + ? groupElement + : [groupElement] + for (let group of groupElements) { + if (!allowedGroupsSet.has(group)) { + invalidGroups.push(group) } } } if (invalidGroups.length > 0) { throw new Error(`Invalid group(s): ${invalidGroups.join(', ')}`) } - validateNoDuplicatedGroups(groups) -} - -/** - * Throws an error if a group is specified more than once - * @param {Group[]} groups - The groups to check for duplicates. - * @throws Will throw an error if duplicated groups are found. - */ -export let validateNoDuplicatedGroups = (groups: Group[]): void => { - let flattenGroups = groups.flat() - let seenGroups = new Set() - let duplicatedGroups = new Set() - - for (let group of flattenGroups) { - if (isNewlinesBetweenOption(group)) { - continue - } - if (seenGroups.has(group)) { - duplicatedGroups.add(group) - } else { - seenGroups.add(group) - } - } - if (duplicatedGroups.size > 0) { - throw new Error(`Duplicated group(s): ${[...duplicatedGroups].join(', ')}`) - } + validateNoDuplicatedGroups(options) + validateNewlinesBetweenInsideGroups(options) } diff --git a/utils/validate-newlines-between-inside-groups.ts b/utils/validate-newlines-between-inside-groups.ts new file mode 100644 index 00000000..81b9f3a6 --- /dev/null +++ b/utils/validate-newlines-between-inside-groups.ts @@ -0,0 +1,22 @@ +import type { GroupsOptions } from '../types/common-options' + +import { isNewlinesBetweenOption } from './is-newlines-between-option' + +export let validateNewlinesBetweenInsideGroups = ({ + groups, +}: { + groups: GroupsOptions +}): void => { + let isPreviousElementNewlinesBetween = false + for (let groupElement of groups) { + if (!isNewlinesBetweenOption(groupElement)) { + isPreviousElementNewlinesBetween = false + continue + } + // There should not be two consecutive `newlinesBetween` objects + if (isPreviousElementNewlinesBetween) { + throw new Error("Consecutive 'newlinesBetween' objects are not allowed") + } + isPreviousElementNewlinesBetween = true + } +} diff --git a/utils/validate-no-duplicated-groups.ts b/utils/validate-no-duplicated-groups.ts new file mode 100644 index 00000000..3a94fd33 --- /dev/null +++ b/utils/validate-no-duplicated-groups.ts @@ -0,0 +1,34 @@ +import type { GroupsOptions } from '../types/common-options' + +import { isNewlinesBetweenOption } from './is-newlines-between-option' + +/** + * Throws an error if a group is specified more than once + * @param {object} parameters - Parameters object. + * @param {GroupsOptions} parameters.groups - The groups to check for duplicates. + * @throws Error Will throw an error if duplicated groups are found. + */ +export let validateNoDuplicatedGroups = ({ + groups, +}: { + groups: GroupsOptions +}): void => { + let flattenGroups = groups.flat() + let seenGroups = new Set() + let duplicatedGroups = new Set() + + for (let group of flattenGroups) { + if (isNewlinesBetweenOption(group)) { + continue + } + if (seenGroups.has(group)) { + duplicatedGroups.add(group) + } else { + seenGroups.add(group) + } + } + + if (duplicatedGroups.size > 0) { + throw new Error(`Duplicated group(s): ${[...duplicatedGroups].join(', ')}`) + } +}