routie dev init since i didn't adhere to any proper guidance up until now
This commit is contained in:
Generated
Vendored
+223
@@ -0,0 +1,223 @@
|
||||
import { computeGroupsNames } from './compute-groups-names.js'
|
||||
import { validateObjectsInsideGroups } from './validate-objects-inside-groups.js'
|
||||
import { validateNoDuplicatedGroups } from './validate-no-duplicated-groups.js'
|
||||
/**
|
||||
* Validates that all groups in configuration are either predefined or custom.
|
||||
*
|
||||
* Ensures that every group specified in the configuration either:
|
||||
*
|
||||
* 1. Is a valid predefined group (combination of modifiers and selectors)
|
||||
* 2. Is defined in customGroups
|
||||
* 3. Is the special 'unknown' group.
|
||||
*
|
||||
* Also validates that there are no duplicate groups.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Valid predefined groups for React imports
|
||||
* validateGeneratedGroupsConfiguration({
|
||||
* options: {
|
||||
* groups: ['react', 'external', 'internal', 'side-effect-import'],
|
||||
* customGroups: [],
|
||||
* },
|
||||
* selectors: ['import', 'export'],
|
||||
* modifiers: ['side-effect', 'type', 'value'],
|
||||
* })
|
||||
* // All groups are valid predefined groups
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Invalid group that doesn't exist
|
||||
* validateGeneratedGroupsConfiguration({
|
||||
* options: {
|
||||
* groups: ['my-special-group'], // Not predefined, not in customGroups
|
||||
* customGroups: [],
|
||||
* },
|
||||
* selectors: ['property', 'method'],
|
||||
* modifiers: ['static', 'private'],
|
||||
* })
|
||||
* // Throws: Error: Invalid group(s): my-special-group
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Valid with custom groups for class members
|
||||
* validateGeneratedGroupsConfiguration({
|
||||
* options: {
|
||||
* groups: ['static-property', 'constructor', 'lifecycle-methods'],
|
||||
* customGroups: [
|
||||
* {
|
||||
* groupName: 'lifecycle-methods',
|
||||
* elementNamePattern: [
|
||||
* /^componentDidMount$/,
|
||||
* /^componentWillUnmount$/,
|
||||
* ],
|
||||
* },
|
||||
* ],
|
||||
* },
|
||||
* selectors: ['property', 'method', 'constructor'],
|
||||
* modifiers: ['static', 'private', 'public'],
|
||||
* })
|
||||
* // 'static-property' is predefined, 'lifecycle-methods' is custom
|
||||
* ```
|
||||
*
|
||||
* @param params - Configuration parameters to validate.
|
||||
* @throws {Error} If any group is neither predefined nor custom.
|
||||
*/
|
||||
function validateGroupsConfiguration({ selectors, modifiers, options }) {
|
||||
let availableCustomGroupNames = new Set(
|
||||
options.customGroups.map(customGroup => customGroup.groupName),
|
||||
)
|
||||
let invalidGroups = computeGroupsNames(options.groups).filter(
|
||||
group =>
|
||||
!isPredefinedGroup(selectors, modifiers, group) &&
|
||||
!availableCustomGroupNames.has(group),
|
||||
)
|
||||
if (invalidGroups.length > 0) {
|
||||
throw new Error(`Invalid group(s): ${invalidGroups.join(', ')}`)
|
||||
}
|
||||
validateNoDuplicatedGroups(options)
|
||||
validateObjectsInsideGroups(options)
|
||||
}
|
||||
/**
|
||||
* Checks if a group name is a valid predefined group.
|
||||
*
|
||||
* Predefined groups are formed by combining modifiers and selectors with
|
||||
* dashes. The function parses the group name from right to left, first
|
||||
* extracting the selector (which can be up to 3 words), then the modifiers.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Valid predefined groups
|
||||
* isPredefinedGroup(
|
||||
* ['property', 'method'],
|
||||
* ['static', 'private'],
|
||||
* 'static-private-property',
|
||||
* )
|
||||
* // Returns: true (static + private + property)
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Special 'unknown' group
|
||||
* isPredefinedGroup([], [], 'unknown')
|
||||
* // Returns: true (always valid)
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Invalid group - not matching selectors
|
||||
* isPredefinedGroup(
|
||||
* ['import', 'export'],
|
||||
* ['type', 'value'],
|
||||
* 'custom-group',
|
||||
* )
|
||||
* // Returns: false ('group' is not a valid selector)
|
||||
* ```
|
||||
*
|
||||
* @param allSelectors - Available selectors for the rule.
|
||||
* @param allModifiers - Available modifiers for the rule.
|
||||
* @param input - Group name to validate.
|
||||
* @returns True if the group is a valid predefined combination.
|
||||
*/
|
||||
function isPredefinedGroup(allSelectors, allModifiers, input) {
|
||||
if (input === 'unknown') {
|
||||
return true
|
||||
}
|
||||
let elementsSeparatedWithDash = input.split('-')
|
||||
let longestAllowedSelector = computeLongestAllowedWord({
|
||||
allowedValues: allSelectors,
|
||||
elementsSeparatedWithDash,
|
||||
})
|
||||
if (!longestAllowedSelector) {
|
||||
return false
|
||||
}
|
||||
let modifiersToParse = elementsSeparatedWithDash.slice(
|
||||
0,
|
||||
-longestAllowedSelector.wordCount,
|
||||
)
|
||||
let parsedModifiers = /* @__PURE__ */ new Set()
|
||||
while (modifiersToParse.length > 0) {
|
||||
let longestAllowedModifier = computeLongestAllowedWord({
|
||||
elementsSeparatedWithDash: modifiersToParse,
|
||||
allowedValues: allModifiers,
|
||||
})
|
||||
if (!longestAllowedModifier) {
|
||||
return false
|
||||
}
|
||||
if (parsedModifiers.has(longestAllowedModifier.word)) {
|
||||
return false
|
||||
}
|
||||
parsedModifiers.add(longestAllowedModifier.word)
|
||||
modifiersToParse = modifiersToParse.slice(
|
||||
0,
|
||||
-longestAllowedModifier.wordCount,
|
||||
)
|
||||
}
|
||||
return true
|
||||
}
|
||||
/**
|
||||
* Finds the longest valid word from the end of a dash-separated array.
|
||||
*
|
||||
* Attempts to match 3-word, 2-word, then 1-word combinations from the end of
|
||||
* the array against allowed values. This is used to parse selectors and
|
||||
* modifiers from group names.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Matching a multi-word selector
|
||||
* computeLongestAllowedWord({
|
||||
* elementsSeparatedWithDash: ['static', 'get', 'accessor'],
|
||||
* allowedValues: ['accessor', 'get-accessor', 'property'],
|
||||
* })
|
||||
* // Returns: { word: 'get-accessor', wordCount: 2 }
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Matching a single-word modifier
|
||||
* computeLongestAllowedWord({
|
||||
* elementsSeparatedWithDash: ['private', 'static'],
|
||||
* allowedValues: ['static', 'private', 'public'],
|
||||
* })
|
||||
* // Returns: { word: 'static', wordCount: 1 }
|
||||
* ```
|
||||
*
|
||||
* @param params - Parameters for word matching.
|
||||
* @returns Matched word with its word count, or null if no match.
|
||||
*/
|
||||
function computeLongestAllowedWord({
|
||||
elementsSeparatedWithDash,
|
||||
allowedValues,
|
||||
}) {
|
||||
let match = [
|
||||
{
|
||||
word: elementsSeparatedWithDash.slice(-3).join('-'),
|
||||
wordCount: 3,
|
||||
},
|
||||
{
|
||||
word: elementsSeparatedWithDash.slice(-2).join('-'),
|
||||
wordCount: 2,
|
||||
},
|
||||
{
|
||||
word: elementsSeparatedWithDash.at(-1),
|
||||
wordCount: 1,
|
||||
},
|
||||
]
|
||||
.filter(({ wordCount }) => elementsSeparatedWithDash.length >= wordCount)
|
||||
.find(({ word }) => word && allowedValues.includes(word))
|
||||
if (!match) {
|
||||
return null
|
||||
}
|
||||
return match
|
||||
}
|
||||
export { validateGroupsConfiguration }
|
||||
Reference in New Issue
Block a user