Skip to content

Commit

Permalink
feat: throw error when a group does not exist or duplicated in sort-c…
Browse files Browse the repository at this point in the history
…lasses
  • Loading branch information
hugop95 authored Oct 9, 2024
1 parent 4e7e5ad commit d447ffb
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 2 deletions.
48 changes: 47 additions & 1 deletion rules/sort-classes-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import type {
} from './sort-classes.types'
import type { CompareOptions } from '../utils/compare'

import { validateNoDuplicatedGroups } from '../utils/validate-groups-configuration'
import { allModifiers, allSelectors } from './sort-classes.types'
import { matches } from '../utils/matches'

interface CustomGroupMatchesProps {
Expand Down Expand Up @@ -72,7 +74,7 @@ export const generateOfficialGroups = (
/**
* Get possible combinations of n elements from an array
*/
const getCombinations = (array: string[], n: number): string[][] => {
export const getCombinations = (array: string[], n: number): string[][] => {
let result: string[][] = []

let backtrack = (start: number, comb: string[]) => {
Expand Down Expand Up @@ -253,3 +255,47 @@ export const getCompareOptions = (
ignoreCase: options.ignoreCase,
}
}

export let validateGroupsConfiguration = (
groups: Required<SortClassesOptions[0]>['groups'],
customGroups: Required<SortClassesOptions[0]>['customGroups'],
): void => {
let availableCustomGroupNames = Array.isArray(customGroups)
? customGroups.map(customGroup => customGroup.groupName)
: Object.keys(customGroups)
let invalidGroups = groups
.flat()
.filter(
group =>
!isPredefinedGroup(group) && !availableCustomGroupNames.includes(group),
)
if (invalidGroups.length) {
throw new Error('Invalid group(s): ' + invalidGroups.join(', '))
}
validateNoDuplicatedGroups(groups)
}

const isPredefinedGroup = (input: string): boolean => {
if (input === 'unknown') {
return true
}
let singleWordSelector = input.split('-').at(-1)
if (!singleWordSelector) {
return false
}
let twoWordsSelector = input.split('-').slice(-2).join('-')
let isTwoWordSelectorValid = allSelectors.includes(
twoWordsSelector as Selector,
)
if (
!allSelectors.includes(singleWordSelector as Selector) &&
!isTwoWordSelectorValid
) {
return false
}
let modifiers = input.split('-').slice(0, isTwoWordSelectorValid ? -2 : -1)
return (
new Set(modifiers).size === modifiers.length &&
modifiers.every(modifier => allModifiers.includes(modifier as Modifier))
)
}
3 changes: 3 additions & 0 deletions rules/sort-classes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {
import type { SortingNodeWithDependencies } from '../utils/sort-nodes-by-dependencies'

import {
validateGroupsConfiguration,
getOverloadSignatureGroups,
generateOfficialGroups,
customGroupMatches,
Expand Down Expand Up @@ -243,6 +244,8 @@ export default createEslintRule<SortClassesOptions, MESSAGE_ID>({
order: 'asc',
} as const)

validateGroupsConfiguration(options.groups, options.customGroups)

let sourceCode = getSourceCode(context)
let className = node.parent.id?.name

Expand Down
87 changes: 86 additions & 1 deletion test/sort-classes-utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { describe, expect, it } from 'vitest'

import { generateOfficialGroups } from '../rules/sort-classes-utils'
import {
validateGroupsConfiguration,
generateOfficialGroups,
getCombinations,
} from '../rules/sort-classes-utils'
import { allModifiers, allSelectors } from '../rules/sort-classes.types'

describe('sort-classes-utils', () => {
it('sort-classes-utils: should generate official groups', () => {
Expand Down Expand Up @@ -44,4 +49,84 @@ describe('sort-classes-utils', () => {
'method',
])
})

describe('validateGroupsConfiguration', () => {
it('allows predefined groups', () => {
let allModifierCombinationPermutations =
getAllNonEmptyCombinations(allModifiers)
let allPredefinedGroups = allSelectors
.map(selector =>
allModifierCombinationPermutations.map(
modifiers => `${modifiers.join('-')}-${selector}`,
),
)
.flat()
.concat(allSelectors)
expect(
validateGroupsConfiguration(allPredefinedGroups, []),
).toBeUndefined()
})

it('allows custom groups with the new API', () => {
expect(
validateGroupsConfiguration(
['static-property', 'myCustomGroup'],
[
{
groupName: 'myCustomGroup',
},
],
),
).toBeUndefined()
})

it('throws an error with predefined groups with duplicate modifiers', () => {
expect(() =>
validateGroupsConfiguration(['static-static-property'], []),
).toThrow('Invalid group(s): static-static-property')
})

it('throws an error if a duplicate group is provided', () => {
expect(() =>
validateGroupsConfiguration(['static-property', 'static-property'], []),
).toThrow('Duplicated group(s): static-property')
})

it('throws an error if invalid groups are provided with the new API', () => {
expect(() =>
validateGroupsConfiguration(
['static-property', 'myCustomGroup', ''],
[
{
groupName: 'myCustomGroupNotReferenced',
},
],
),
).toThrow('Invalid group(s): myCustomGroup')
})

it('allows groups with the old API', () => {
expect(
validateGroupsConfiguration(['static-property', 'myCustomGroup'], {
myCustomGroup: 'foo',
}),
).toBeUndefined()
})

it('throws an error if invalid custom groups are provided with the old API', () => {
expect(() =>
validateGroupsConfiguration(['static-property', 'myCustomGroup'], {
myCustomGroupNotReferenced: 'foo',
}),
).toThrow('Invalid group(s): myCustomGroup')
})
})
})

const getAllNonEmptyCombinations = (array: string[]): string[][] => {
let result: string[][] = []
for (let i = 1; i < array.length; i++) {
result = [...result, ...getCombinations(array, i)]
}
return result
}
10 changes: 10 additions & 0 deletions test/validate-groups-configuration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,14 @@ describe('validate-groups-configuration', () => {
)
}).toThrow('Invalid group(s): invalidGroup1, invalidGroup2')
})

it('throws an error when a duplicate group is provided', () => {
expect(() => {
validateGroupsConfiguration(
['predefinedGroup', 'predefinedGroup'],
['predefinedGroup'],
[],
)
}).toThrow('Duplicated group(s): predefinedGroup')
})
})
22 changes: 22 additions & 0 deletions utils/validate-groups-configuration.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
/**
* 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
*/
export let validateGroupsConfiguration = (
groups: (string[] | string)[],
allowedPredefinedGroups: string[],
Expand All @@ -13,4 +19,20 @@ export let validateGroupsConfiguration = (
if (invalidGroups.length) {
throw new Error('Invalid group(s): ' + invalidGroups.join(', '))
}
validateNoDuplicatedGroups(groups)
}

/**
* Throws an error if a group is specified more than once
*/
export let validateNoDuplicatedGroups = (
groups: (string[] | string)[],
): void => {
let flattenGroups = groups.flat()
let duplicatedGroups = flattenGroups.filter(
(group, index) => flattenGroups.indexOf(group) !== index,
)
if (duplicatedGroups.length) {
throw new Error('Duplicated group(s): ' + duplicatedGroups.join(', '))
}
}

0 comments on commit d447ffb

Please sign in to comment.