routie dev init since i didn't adhere to any proper guidance up until now
This commit is contained in:
+219
@@ -0,0 +1,219 @@
|
||||
/**
|
||||
* Manages a customizable alphabet for sorting operations.
|
||||
*
|
||||
* Provides methods to generate, modify, and sort alphabets based on various
|
||||
* criteria including Unicode ranges, case prioritization, and locale-specific
|
||||
* ordering. Used by the 'custom' sort type to define character precedence.
|
||||
*
|
||||
* The class supports:
|
||||
*
|
||||
* - Generating alphabets from strings or Unicode ranges
|
||||
* - Case prioritization and manipulation
|
||||
* - Character positioning and removal
|
||||
* - Multiple sorting strategies (natural, locale, char code).
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Create a custom alphabet with specific character order
|
||||
* const alphabet = Alphabet.generateFrom('aAbBcC')
|
||||
* .prioritizeCase('uppercase')
|
||||
* .getCharacters()
|
||||
* // Returns: 'AaBbCc'
|
||||
* ```
|
||||
*/
|
||||
export declare class Alphabet {
|
||||
private characters
|
||||
private constructor()
|
||||
/**
|
||||
* Generates an alphabet from the given characters.
|
||||
*
|
||||
* @param values - The characters to generate the alphabet from.
|
||||
* @returns - The wrapped alphabet.
|
||||
*/
|
||||
static generateFrom(values: string[] | string): Alphabet
|
||||
/**
|
||||
* Generates an alphabet containing relevant characters from the Unicode
|
||||
* standard. Contains the Unicode planes 0 and 1.
|
||||
*
|
||||
* @returns - The generated alphabet.
|
||||
*/
|
||||
static generateRecommendedAlphabet(): Alphabet
|
||||
/**
|
||||
* Generates an alphabet containing all characters from the Unicode standard
|
||||
* except for irrelevant Unicode planes. Contains the Unicode planes 0, 1, 2
|
||||
* and 3.
|
||||
*
|
||||
* @returns - The generated alphabet.
|
||||
*/
|
||||
static generateCompleteAlphabet(): Alphabet
|
||||
private static getCharactersWithCase
|
||||
/**
|
||||
* Generates an alphabet containing relevant characters from the Unicode
|
||||
* standard.
|
||||
*
|
||||
* @param maxCodePoint - The maximum code point to generate the alphabet to.
|
||||
* @returns - The generated alphabet.
|
||||
*/
|
||||
private static generateAlphabetToRange
|
||||
/**
|
||||
* For each character with a lower and upper case, permutes the two cases so
|
||||
* that the alphabet is ordered by the case priority entered.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* Alphabet.generateFrom('aAbBcdCD').prioritizeCase('uppercase') // Returns 'AaBbCDcd'.
|
||||
* ```
|
||||
*
|
||||
* @param casePriority - The case to prioritize.
|
||||
* @returns - The same alphabet instance with the cases prioritized.
|
||||
*/
|
||||
prioritizeCase(casePriority: 'lowercase' | 'uppercase'): this
|
||||
/**
|
||||
* Adds specific characters to the end of the alphabet.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* Alphabet.generateFrom('ab').pushCharacters('cd')
|
||||
* // Returns 'abcd'
|
||||
* ```
|
||||
*
|
||||
* @param values - The characters to push to the alphabet.
|
||||
* @returns - The same alphabet instance without the specified characters.
|
||||
*/
|
||||
pushCharacters(values: string[] | string): this
|
||||
/**
|
||||
* Permutes characters with cases so that all characters with the entered
|
||||
* case are put before the other characters.
|
||||
*
|
||||
* @param caseToComeFirst - The case to put before the other characters.
|
||||
* @returns - The same alphabet instance with all characters with case
|
||||
* before all the characters with the other case.
|
||||
*/
|
||||
placeAllWithCaseBeforeAllWithOtherCase(
|
||||
caseToComeFirst: 'uppercase' | 'lowercase',
|
||||
): this
|
||||
/**
|
||||
* Places a specific character right before another character in the
|
||||
* alphabet.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* Alphabet.generateFrom('ab-cd/').placeCharacterBefore({
|
||||
* characterBefore: '/',
|
||||
* characterAfter: '-',
|
||||
* })
|
||||
* // Returns 'ab/-cd'
|
||||
* ```
|
||||
*
|
||||
* @param params - The parameters for the operation.
|
||||
* @param params.characterBefore - The character to come before
|
||||
* characterAfter.
|
||||
* @param params.characterAfter - The target character.
|
||||
* @returns - The same alphabet instance with the specific character
|
||||
* prioritized.
|
||||
*/
|
||||
placeCharacterBefore({
|
||||
characterBefore,
|
||||
characterAfter,
|
||||
}: {
|
||||
characterBefore: string
|
||||
characterAfter: string
|
||||
}): this
|
||||
/**
|
||||
* Places a specific character right after another character in the
|
||||
* alphabet.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* Alphabet.generateFrom('ab-cd/').placeCharacterAfter({
|
||||
* characterBefore: '/',
|
||||
* characterAfter: '-',
|
||||
* })
|
||||
* // Returns 'abcd/-'
|
||||
* ```
|
||||
*
|
||||
* @param params - The parameters for the operation.
|
||||
* @param params.characterBefore - The target character.
|
||||
* @param params.characterAfter - The character to come after
|
||||
* characterBefore.
|
||||
* @returns - The same alphabet instance with the specific character
|
||||
* prioritized.
|
||||
*/
|
||||
placeCharacterAfter({
|
||||
characterBefore,
|
||||
characterAfter,
|
||||
}: {
|
||||
characterBefore: string
|
||||
characterAfter: string
|
||||
}): this
|
||||
/**
|
||||
* Removes specific characters from the alphabet by their range.
|
||||
*
|
||||
* @param range - The Unicode range to remove characters from.
|
||||
* @param range.start - The starting Unicode codepoint.
|
||||
* @param range.end - The ending Unicode codepoint.
|
||||
* @returns - The same alphabet instance without the characters from the
|
||||
* specified range.
|
||||
*/
|
||||
removeUnicodeRange({ start, end }: { start: number; end: number }): this
|
||||
/**
|
||||
* Sorts the alphabet by the sorting function provided.
|
||||
*
|
||||
* @param sortingFunction - The sorting function to use.
|
||||
* @returns - The same alphabet instance sorted by the sorting function
|
||||
* provided.
|
||||
*/
|
||||
sortBy(
|
||||
sortingFunction: (characterA: string, characterB: string) => number,
|
||||
): this
|
||||
/**
|
||||
* Sorts the alphabet by the natural order of the characters using
|
||||
* `natural-orderby`.
|
||||
*
|
||||
* @param locale - The locale to use for sorting.
|
||||
* @returns - The same alphabet instance sorted by the natural order of the
|
||||
* characters.
|
||||
*/
|
||||
sortByNaturalSort(locale?: string): this
|
||||
/**
|
||||
* Removes specific characters from the alphabet.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* Alphabet.generateFrom('abcd').removeCharacters('dcc')
|
||||
* // Returns 'ab'
|
||||
* ```
|
||||
*
|
||||
* @param values - The characters to remove from the alphabet.
|
||||
* @returns - The same alphabet instance without the specified characters.
|
||||
*/
|
||||
removeCharacters(values: string[] | string): this
|
||||
/**
|
||||
* Sorts the alphabet by the character code point.
|
||||
*
|
||||
* @returns - The same alphabet instance sorted by the character code point.
|
||||
*/
|
||||
sortByCharCodeAt(): this
|
||||
/**
|
||||
* Sorts the alphabet by the locale order of the characters.
|
||||
*
|
||||
* @param locales - The locales to use for sorting.
|
||||
* @returns - The same alphabet instance sorted by the locale order of the
|
||||
* characters.
|
||||
*/
|
||||
sortByLocaleCompare(locales?: Intl.LocalesArgument): this
|
||||
/**
|
||||
* Retrieves the characters from the alphabet.
|
||||
*
|
||||
* @returns The characters from the alphabet.
|
||||
*/
|
||||
getCharacters(): string
|
||||
private placeCharacterBeforeOrAfter
|
||||
private getCharactersWithCase
|
||||
}
|
||||
+397
@@ -0,0 +1,397 @@
|
||||
import { convertBooleanToSign } from './convert-boolean-to-sign.js'
|
||||
import { compare } from 'natural-orderby'
|
||||
/**
|
||||
* Manages a customizable alphabet for sorting operations.
|
||||
*
|
||||
* Provides methods to generate, modify, and sort alphabets based on various
|
||||
* criteria including Unicode ranges, case prioritization, and locale-specific
|
||||
* ordering. Used by the 'custom' sort type to define character precedence.
|
||||
*
|
||||
* The class supports:
|
||||
*
|
||||
* - Generating alphabets from strings or Unicode ranges
|
||||
* - Case prioritization and manipulation
|
||||
* - Character positioning and removal
|
||||
* - Multiple sorting strategies (natural, locale, char code).
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Create a custom alphabet with specific character order
|
||||
* const alphabet = Alphabet.generateFrom('aAbBcC')
|
||||
* .prioritizeCase('uppercase')
|
||||
* .getCharacters()
|
||||
* // Returns: 'AaBbCc'
|
||||
* ```
|
||||
*/
|
||||
var Alphabet = class Alphabet {
|
||||
characters = []
|
||||
constructor(characters) {
|
||||
this.characters = characters
|
||||
}
|
||||
/**
|
||||
* Generates an alphabet from the given characters.
|
||||
*
|
||||
* @param values - The characters to generate the alphabet from.
|
||||
* @returns - The wrapped alphabet.
|
||||
*/
|
||||
static generateFrom(values) {
|
||||
let arrayValues = typeof values === 'string' ? [...values] : values
|
||||
if (arrayValues.length !== new Set(arrayValues).size) {
|
||||
throw new Error('The alphabet must not contain repeated characters')
|
||||
}
|
||||
if (arrayValues.some(value => value.length !== 1)) {
|
||||
throw new Error('The alphabet must contain single characters')
|
||||
}
|
||||
return new Alphabet(
|
||||
arrayValues.map(value =>
|
||||
Alphabet.getCharactersWithCase(value.codePointAt(0)),
|
||||
),
|
||||
)
|
||||
}
|
||||
/**
|
||||
* Generates an alphabet containing relevant characters from the Unicode
|
||||
* standard. Contains the Unicode planes 0 and 1.
|
||||
*
|
||||
* @returns - The generated alphabet.
|
||||
*/
|
||||
static generateRecommendedAlphabet() {
|
||||
return Alphabet.generateAlphabetToRange(131072)
|
||||
}
|
||||
/**
|
||||
* Generates an alphabet containing all characters from the Unicode standard
|
||||
* except for irrelevant Unicode planes. Contains the Unicode planes 0, 1, 2
|
||||
* and 3.
|
||||
*
|
||||
* @returns - The generated alphabet.
|
||||
*/
|
||||
static generateCompleteAlphabet() {
|
||||
return Alphabet.generateAlphabetToRange(262144)
|
||||
}
|
||||
static getCharactersWithCase(codePoint) {
|
||||
let character = String.fromCodePoint(codePoint)
|
||||
let lowercaseCharacter = character.toLowerCase()
|
||||
let uppercaseCharacter = character.toUpperCase()
|
||||
return {
|
||||
value: character,
|
||||
codePoint,
|
||||
...(lowercaseCharacter === character ? null : (
|
||||
{ lowercaseCharacterCodePoint: lowercaseCharacter.codePointAt(0) }
|
||||
)),
|
||||
...(uppercaseCharacter === character ? null : (
|
||||
{ uppercaseCharacterCodePoint: uppercaseCharacter.codePointAt(0) }
|
||||
)),
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Generates an alphabet containing relevant characters from the Unicode
|
||||
* standard.
|
||||
*
|
||||
* @param maxCodePoint - The maximum code point to generate the alphabet to.
|
||||
* @returns - The generated alphabet.
|
||||
*/
|
||||
static generateAlphabetToRange(maxCodePoint) {
|
||||
return new Alphabet(
|
||||
Array.from({ length: maxCodePoint }, (_, i) =>
|
||||
Alphabet.getCharactersWithCase(i),
|
||||
),
|
||||
)
|
||||
}
|
||||
/**
|
||||
* For each character with a lower and upper case, permutes the two cases so
|
||||
* that the alphabet is ordered by the case priority entered.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* Alphabet.generateFrom('aAbBcdCD').prioritizeCase('uppercase') // Returns 'AaBbCDcd'.
|
||||
* ```
|
||||
*
|
||||
* @param casePriority - The case to prioritize.
|
||||
* @returns - The same alphabet instance with the cases prioritized.
|
||||
*/
|
||||
prioritizeCase(casePriority) {
|
||||
let charactersWithCase = this.getCharactersWithCase()
|
||||
let parsedIndexes = /* @__PURE__ */ new Set()
|
||||
let indexByCodePoints = this.characters.reduce(
|
||||
(indexByCodePoint, character, index) => {
|
||||
indexByCodePoint[character.codePoint] = index
|
||||
return indexByCodePoint
|
||||
},
|
||||
{},
|
||||
)
|
||||
for (let { character, index } of charactersWithCase) {
|
||||
if (parsedIndexes.has(index)) {
|
||||
continue
|
||||
}
|
||||
parsedIndexes.add(index)
|
||||
let otherCharacterIndex =
|
||||
indexByCodePoints[
|
||||
character.uppercaseCharacterCodePoint ??
|
||||
character.lowercaseCharacterCodePoint
|
||||
]
|
||||
if (otherCharacterIndex === void 0) {
|
||||
continue
|
||||
}
|
||||
parsedIndexes.add(otherCharacterIndex)
|
||||
if (!character.uppercaseCharacterCodePoint) {
|
||||
if (
|
||||
(casePriority === 'uppercase' && index < otherCharacterIndex) ||
|
||||
(casePriority === 'lowercase' && index > otherCharacterIndex)
|
||||
) {
|
||||
continue
|
||||
}
|
||||
} else if (
|
||||
(casePriority === 'uppercase' && index > otherCharacterIndex) ||
|
||||
(casePriority === 'lowercase' && index < otherCharacterIndex)
|
||||
) {
|
||||
continue
|
||||
}
|
||||
this.characters[index] = this.characters[otherCharacterIndex]
|
||||
this.characters[otherCharacterIndex] = character
|
||||
}
|
||||
return this
|
||||
}
|
||||
/**
|
||||
* Adds specific characters to the end of the alphabet.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* Alphabet.generateFrom('ab').pushCharacters('cd')
|
||||
* // Returns 'abcd'
|
||||
* ```
|
||||
*
|
||||
* @param values - The characters to push to the alphabet.
|
||||
* @returns - The same alphabet instance without the specified characters.
|
||||
*/
|
||||
pushCharacters(values) {
|
||||
let arrayValues = typeof values === 'string' ? [...values] : values
|
||||
let valuesSet = new Set(arrayValues)
|
||||
let valuesAlreadyExisting = this.characters.filter(({ value }) =>
|
||||
valuesSet.has(value),
|
||||
)
|
||||
if (valuesAlreadyExisting.length > 0) {
|
||||
throw new Error(
|
||||
`The alphabet already contains the characters ${valuesAlreadyExisting
|
||||
.slice(0, 5)
|
||||
.map(({ value }) => value)
|
||||
.join(', ')}`,
|
||||
)
|
||||
}
|
||||
if (arrayValues.some(value => value.length !== 1)) {
|
||||
throw new Error('Only single characters may be pushed')
|
||||
}
|
||||
this.characters.push(
|
||||
...[...valuesSet].map(value =>
|
||||
Alphabet.getCharactersWithCase(value.codePointAt(0)),
|
||||
),
|
||||
)
|
||||
return this
|
||||
}
|
||||
/**
|
||||
* Permutes characters with cases so that all characters with the entered case
|
||||
* are put before the other characters.
|
||||
*
|
||||
* @param caseToComeFirst - The case to put before the other characters.
|
||||
* @returns - The same alphabet instance with all characters with case before
|
||||
* all the characters with the other case.
|
||||
*/
|
||||
placeAllWithCaseBeforeAllWithOtherCase(caseToComeFirst) {
|
||||
let otherCaseKey =
|
||||
caseToComeFirst === 'uppercase' ?
|
||||
'uppercaseCharacterCodePoint'
|
||||
: 'lowercaseCharacterCodePoint'
|
||||
let keep = []
|
||||
let move = []
|
||||
for (let character of this.characters) {
|
||||
;(character[otherCaseKey] === void 0 ? keep : move).push(character)
|
||||
}
|
||||
this.characters = [...keep, ...move]
|
||||
return this
|
||||
}
|
||||
/**
|
||||
* Places a specific character right before another character in the alphabet.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* Alphabet.generateFrom('ab-cd/').placeCharacterBefore({
|
||||
* characterBefore: '/',
|
||||
* characterAfter: '-',
|
||||
* })
|
||||
* // Returns 'ab/-cd'
|
||||
* ```
|
||||
*
|
||||
* @param params - The parameters for the operation.
|
||||
* @param params.characterBefore - The character to come before
|
||||
* characterAfter.
|
||||
* @param params.characterAfter - The target character.
|
||||
* @returns - The same alphabet instance with the specific character
|
||||
* prioritized.
|
||||
*/
|
||||
placeCharacterBefore({ characterBefore, characterAfter }) {
|
||||
return this.placeCharacterBeforeOrAfter({
|
||||
characterBefore,
|
||||
characterAfter,
|
||||
type: 'before',
|
||||
})
|
||||
}
|
||||
/**
|
||||
* Places a specific character right after another character in the alphabet.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* Alphabet.generateFrom('ab-cd/').placeCharacterAfter({
|
||||
* characterBefore: '/',
|
||||
* characterAfter: '-',
|
||||
* })
|
||||
* // Returns 'abcd/-'
|
||||
* ```
|
||||
*
|
||||
* @param params - The parameters for the operation.
|
||||
* @param params.characterBefore - The target character.
|
||||
* @param params.characterAfter - The character to come after characterBefore.
|
||||
* @returns - The same alphabet instance with the specific character
|
||||
* prioritized.
|
||||
*/
|
||||
placeCharacterAfter({ characterBefore, characterAfter }) {
|
||||
return this.placeCharacterBeforeOrAfter({
|
||||
characterBefore,
|
||||
characterAfter,
|
||||
type: 'after',
|
||||
})
|
||||
}
|
||||
/**
|
||||
* Removes specific characters from the alphabet by their range.
|
||||
*
|
||||
* @param range - The Unicode range to remove characters from.
|
||||
* @param range.start - The starting Unicode codepoint.
|
||||
* @param range.end - The ending Unicode codepoint.
|
||||
* @returns - The same alphabet instance without the characters from the
|
||||
* specified range.
|
||||
*/
|
||||
removeUnicodeRange({ start, end }) {
|
||||
this.characters = this.characters.filter(
|
||||
({ codePoint }) => codePoint < start || codePoint > end,
|
||||
)
|
||||
return this
|
||||
}
|
||||
/**
|
||||
* Sorts the alphabet by the sorting function provided.
|
||||
*
|
||||
* @param sortingFunction - The sorting function to use.
|
||||
* @returns - The same alphabet instance sorted by the sorting function
|
||||
* provided.
|
||||
*/
|
||||
sortBy(sortingFunction) {
|
||||
this.characters.sort((a, b) => sortingFunction(a.value, b.value))
|
||||
return this
|
||||
}
|
||||
/**
|
||||
* Sorts the alphabet by the natural order of the characters using
|
||||
* `natural-orderby`.
|
||||
*
|
||||
* @param locale - The locale to use for sorting.
|
||||
* @returns - The same alphabet instance sorted by the natural order of the
|
||||
* characters.
|
||||
*/
|
||||
sortByNaturalSort(locale) {
|
||||
let naturalCompare = compare({ locale })
|
||||
return this.sortBy((a, b) => naturalCompare(a, b))
|
||||
}
|
||||
/**
|
||||
* Removes specific characters from the alphabet.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* Alphabet.generateFrom('abcd').removeCharacters('dcc')
|
||||
* // Returns 'ab'
|
||||
* ```
|
||||
*
|
||||
* @param values - The characters to remove from the alphabet.
|
||||
* @returns - The same alphabet instance without the specified characters.
|
||||
*/
|
||||
removeCharacters(values) {
|
||||
this.characters = this.characters.filter(
|
||||
({ value }) => !values.includes(value),
|
||||
)
|
||||
return this
|
||||
}
|
||||
/**
|
||||
* Sorts the alphabet by the character code point.
|
||||
*
|
||||
* @returns - The same alphabet instance sorted by the character code point.
|
||||
*/
|
||||
sortByCharCodeAt() {
|
||||
return this.sortBy((a, b) =>
|
||||
convertBooleanToSign(a.charCodeAt(0) > b.charCodeAt(0)),
|
||||
)
|
||||
}
|
||||
/**
|
||||
* Sorts the alphabet by the locale order of the characters.
|
||||
*
|
||||
* @param locales - The locales to use for sorting.
|
||||
* @returns - The same alphabet instance sorted by the locale order of the
|
||||
* characters.
|
||||
*/
|
||||
sortByLocaleCompare(locales) {
|
||||
return this.sortBy((a, b) => a.localeCompare(b, locales))
|
||||
}
|
||||
/**
|
||||
* Retrieves the characters from the alphabet.
|
||||
*
|
||||
* @returns The characters from the alphabet.
|
||||
*/
|
||||
getCharacters() {
|
||||
return this.characters.map(({ value }) => value).join('')
|
||||
}
|
||||
placeCharacterBeforeOrAfter({ characterBefore, characterAfter, type }) {
|
||||
let indexOfCharacterAfter = this.characters.findIndex(
|
||||
({ value }) => value === characterAfter,
|
||||
)
|
||||
let indexOfCharacterBefore = this.characters.findIndex(
|
||||
({ value }) => value === characterBefore,
|
||||
)
|
||||
if (indexOfCharacterAfter === -1) {
|
||||
throw new Error(`Character ${characterAfter} not found in alphabet`)
|
||||
}
|
||||
if (indexOfCharacterBefore === -1) {
|
||||
throw new Error(`Character ${characterBefore} not found in alphabet`)
|
||||
}
|
||||
if (indexOfCharacterBefore <= indexOfCharacterAfter) {
|
||||
return this
|
||||
}
|
||||
this.characters.splice(
|
||||
type === 'before' ? indexOfCharacterAfter : indexOfCharacterBefore + 1,
|
||||
0,
|
||||
this.characters[
|
||||
type === 'before' ? indexOfCharacterBefore : indexOfCharacterAfter
|
||||
],
|
||||
)
|
||||
this.characters.splice(
|
||||
type === 'before' ? indexOfCharacterBefore + 1 : indexOfCharacterAfter,
|
||||
1,
|
||||
)
|
||||
return this
|
||||
}
|
||||
getCharactersWithCase() {
|
||||
return this.characters
|
||||
.map((character, index) => {
|
||||
if (
|
||||
!character.uppercaseCharacterCodePoint &&
|
||||
!character.lowercaseCharacterCodePoint
|
||||
) {
|
||||
return null
|
||||
}
|
||||
return {
|
||||
character,
|
||||
index,
|
||||
}
|
||||
})
|
||||
.filter(element => element !== null)
|
||||
}
|
||||
}
|
||||
export { Alphabet }
|
||||
Generated
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Assert that the given member is of type `never`. This is useful for
|
||||
* exhaustive checks in switch statements or conditional logic.
|
||||
*
|
||||
* @param _member - The member to check, which should be of type `never`.
|
||||
*/
|
||||
export declare function assertIsNever(_member: never): void
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Assert that the given member is of type `never`. This is useful for
|
||||
* exhaustive checks in switch statements or conditional logic.
|
||||
*
|
||||
* @param _member - The member to check, which should be of type `never`.
|
||||
*/
|
||||
function assertIsNever(_member) {}
|
||||
export { assertIsNever }
|
||||
Generated
Vendored
+46
@@ -0,0 +1,46 @@
|
||||
import { RuleContext } from '@typescript-eslint/utils/ts-eslint'
|
||||
import { AST_NODE_TYPES } from '@typescript-eslint/utils'
|
||||
import { NodeOfType } from '../types/node-of-type.js'
|
||||
type Sorter<
|
||||
MessageId extends string,
|
||||
Options extends BaseOptions[],
|
||||
NodeTypes extends AST_NODE_TYPES,
|
||||
> = (parameters: {
|
||||
context: Readonly<RuleContext<MessageId, Options>>
|
||||
matchedAstSelectors: ReadonlySet<string>
|
||||
node: NodeOfType<NodeTypes>
|
||||
}) => void
|
||||
type AstListeners<NodeTypes extends AST_NODE_TYPES> = Record<
|
||||
string,
|
||||
(node: NodeOfType<NodeTypes>) => void
|
||||
>
|
||||
interface BaseOptions {
|
||||
useConfigurationIf?: {
|
||||
matchesAstSelector?: string
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Builds the AST listeners for the rule based on the provided node types,
|
||||
* context, and sorter function.
|
||||
*
|
||||
* @param params - The parameters object.
|
||||
* @param params.nodeTypes - The AST node types to listen for.
|
||||
* @param params.context - The rule context.
|
||||
* @param params.sorter - The function that sorts the nodes based on the
|
||||
* provided parameters.
|
||||
* @returns An object containing the AST listeners for the specified node types.
|
||||
*/
|
||||
export declare function buildAstListeners<
|
||||
MessageId extends string,
|
||||
Options extends BaseOptions[],
|
||||
NodeTypes extends AST_NODE_TYPES,
|
||||
>({
|
||||
nodeTypes,
|
||||
context,
|
||||
sorter,
|
||||
}: {
|
||||
sorter: Sorter<MessageId, Options, NoInfer<NodeTypes>>
|
||||
context: Readonly<RuleContext<MessageId, Options>>
|
||||
nodeTypes: NodeTypes[]
|
||||
}): AstListeners<NodeTypes>
|
||||
export {}
|
||||
Generated
Vendored
+67
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Builds the AST listeners for the rule based on the provided node types,
|
||||
* context, and sorter function.
|
||||
*
|
||||
* @param params - The parameters object.
|
||||
* @param params.nodeTypes - The AST node types to listen for.
|
||||
* @param params.context - The rule context.
|
||||
* @param params.sorter - The function that sorts the nodes based on the
|
||||
* provided parameters.
|
||||
* @returns An object containing the AST listeners for the specified node types.
|
||||
*/
|
||||
function buildAstListeners({ nodeTypes, context, sorter }) {
|
||||
let emptyMatchedAstSelectors = /* @__PURE__ */ new Set()
|
||||
let matchedAstSelectorsByNode = /* @__PURE__ */ new WeakMap()
|
||||
let allAstSelectorMatchers = [
|
||||
...new Set(
|
||||
context.options
|
||||
.map(option => option.useConfigurationIf?.matchesAstSelector)
|
||||
.filter(matchesAstSelector => matchesAstSelector !== void 0),
|
||||
),
|
||||
].map(astSelector => [
|
||||
astSelector,
|
||||
buildMatchedAstSelectorsCollector({
|
||||
matchedAstSelectorsByNode,
|
||||
astSelector,
|
||||
nodeTypes,
|
||||
}),
|
||||
])
|
||||
return {
|
||||
...Object.fromEntries(allAstSelectorMatchers),
|
||||
...Object.fromEntries(nodeTypes.map(buildNodeTypeExitListener)),
|
||||
}
|
||||
function buildNodeTypeExitListener(nodeType) {
|
||||
return [
|
||||
`${nodeType}:exit`,
|
||||
node =>
|
||||
sorter({
|
||||
matchedAstSelectors:
|
||||
matchedAstSelectorsByNode.get(node) ?? emptyMatchedAstSelectors,
|
||||
context,
|
||||
node,
|
||||
}),
|
||||
]
|
||||
}
|
||||
}
|
||||
function buildMatchedAstSelectorsCollector({
|
||||
matchedAstSelectorsByNode,
|
||||
astSelector,
|
||||
nodeTypes,
|
||||
}) {
|
||||
return collectMatchedAstSelectors
|
||||
function collectMatchedAstSelectors(node) {
|
||||
if (!isNodeOfType(node)) {
|
||||
return
|
||||
}
|
||||
let matchedAstSelectors = matchedAstSelectorsByNode.get(node)
|
||||
if (!matchedAstSelectors) {
|
||||
matchedAstSelectors = /* @__PURE__ */ new Set()
|
||||
matchedAstSelectorsByNode.set(node, matchedAstSelectors)
|
||||
}
|
||||
matchedAstSelectors.add(astSelector)
|
||||
}
|
||||
function isNodeOfType(node) {
|
||||
return nodeTypes.includes(node.type)
|
||||
}
|
||||
}
|
||||
export { buildAstListeners }
|
||||
Generated
Vendored
+31
@@ -0,0 +1,31 @@
|
||||
import { OptionsByGroupIndexComputer } from './sort-nodes-by-groups.js'
|
||||
import { CommonGroupsOptions } from '../types/common-groups-options.js'
|
||||
import { CommonOptions } from '../types/common-options.js'
|
||||
type Options = Pick<
|
||||
CommonGroupsOptions<string, unknown, unknown>,
|
||||
'customGroups' | 'groups'
|
||||
> &
|
||||
CommonOptions
|
||||
/**
|
||||
* Creates a function that retrieves overridden options for a specific group
|
||||
* index.
|
||||
*
|
||||
* Returns a closure that captures the options and provides a convenient way to
|
||||
* get overridden options for any group index. This is used in sorting
|
||||
* algorithms that need to apply different sorting rules to different groups.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* const getOverriddenOptions = buildOptionsByGroupIndexComputer(options)
|
||||
* const group1Options = getOverriddenOptions(0)
|
||||
* const group2Options = getOverriddenOptions(1)
|
||||
* ```
|
||||
*
|
||||
* @param options - Base sorting options with group configuration.
|
||||
* @returns Function that takes a group index and returns overridden options.
|
||||
*/
|
||||
export declare function buildOptionsByGroupIndexComputer<T extends Options>(
|
||||
options: T,
|
||||
): OptionsByGroupIndexComputer<T>
|
||||
export {}
|
||||
Generated
Vendored
+24
@@ -0,0 +1,24 @@
|
||||
import { computeOverriddenOptionsByGroupIndex } from './compute-overridden-options-by-group-index.js'
|
||||
/**
|
||||
* Creates a function that retrieves overridden options for a specific group
|
||||
* index.
|
||||
*
|
||||
* Returns a closure that captures the options and provides a convenient way to
|
||||
* get overridden options for any group index. This is used in sorting
|
||||
* algorithms that need to apply different sorting rules to different groups.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* const getOverriddenOptions = buildOptionsByGroupIndexComputer(options)
|
||||
* const group1Options = getOverriddenOptions(0)
|
||||
* const group2Options = getOverriddenOptions(1)
|
||||
* ```
|
||||
*
|
||||
* @param options - Base sorting options with group configuration.
|
||||
* @returns Function that takes a group index and returns overridden options.
|
||||
*/
|
||||
function buildOptionsByGroupIndexComputer(options) {
|
||||
return groupIndex => computeOverriddenOptionsByGroupIndex(options, groupIndex)
|
||||
}
|
||||
export { buildOptionsByGroupIndexComputer }
|
||||
Generated
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
import { SortingNode } from '../types/sorting-node.js'
|
||||
/**
|
||||
* Builds a map from nodes to their corresponding sorting nodes.
|
||||
*
|
||||
* @param sortingNodes - An array of sorting nodes.
|
||||
* @returns A map where each key is a node and the value is its sorting node.
|
||||
*/
|
||||
export declare function buildSortingNodeByNodeMap<
|
||||
T extends Pick<SortingNode, 'node'>,
|
||||
>(sortingNodes: T[]): Map<T['node'], T>
|
||||
Generated
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Builds a map from nodes to their corresponding sorting nodes.
|
||||
*
|
||||
* @param sortingNodes - An array of sorting nodes.
|
||||
* @returns A map where each key is a node and the value is its sorting node.
|
||||
*/
|
||||
function buildSortingNodeByNodeMap(sortingNodes) {
|
||||
let sortingNodeByNode = /* @__PURE__ */ new Map()
|
||||
for (let sortingNode of sortingNodes) {
|
||||
sortingNodeByNode.set(sortingNode.node, sortingNode)
|
||||
}
|
||||
return sortingNodeByNode
|
||||
}
|
||||
export { buildSortingNodeByNodeMap }
|
||||
Generated
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
import { Comparator } from './default-comparator-by-options-computer.js'
|
||||
import { CommonOptions } from '../../types/common-options.js'
|
||||
import { SortingNode } from '../../types/sorting-node.js'
|
||||
/**
|
||||
* Creates a comparator function that sorts nodes by their line length.
|
||||
*
|
||||
* @param options - Options containing the sort order.
|
||||
* @param options.order - The order direction ('asc' or 'desc').
|
||||
* @returns A comparator function that compares two sorting nodes by their size.
|
||||
*/
|
||||
export declare function buildLineLengthComparator({
|
||||
order,
|
||||
}: Pick<CommonOptions, 'order'>): Comparator<SortingNode>
|
||||
frontend/node_modules/eslint-plugin-perfectionist/dist/utils/compare/build-line-length-comparator.js
Generated
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
import { computeOrderedValue } from './compute-ordered-value.js'
|
||||
/**
|
||||
* Creates a comparator function that sorts nodes by their line length.
|
||||
*
|
||||
* @param options - Options containing the sort order.
|
||||
* @param options.order - The order direction ('asc' or 'desc').
|
||||
* @returns A comparator function that compares two sorting nodes by their size.
|
||||
*/
|
||||
function buildLineLengthComparator({ order }) {
|
||||
return (a, b) => {
|
||||
return computeOrderedValue(a.size - b.size, order)
|
||||
}
|
||||
}
|
||||
export { buildLineLengthComparator }
|
||||
Generated
Vendored
+28
@@ -0,0 +1,28 @@
|
||||
import { CommonOptions } from '../../types/common-options.js'
|
||||
/**
|
||||
* Creates a function that formats strings for comparison.
|
||||
*
|
||||
* Applies transformations based on the provided options:
|
||||
*
|
||||
* - Case normalization (lowercase if ignoreCase is true)
|
||||
* - Special character handling (keep, trim, or remove)
|
||||
* - Whitespace removal (always applied).
|
||||
*
|
||||
* @param params - Parameters for string formatting.
|
||||
* @param params.ignoreCase - Whether to convert strings to lowercase.
|
||||
* @param params.specialCharacters - How to handle special characters:
|
||||
*
|
||||
* - 'keep': Keep all characters as-is
|
||||
* - 'trim': Remove leading special characters
|
||||
* - 'remove': Remove all special characters.
|
||||
*
|
||||
* @returns Function that formats a string for comparison.
|
||||
* @throws {UnreachableCaseError} If an unknown special characters option is
|
||||
* specified.
|
||||
*/
|
||||
export declare function buildStringFormatter({
|
||||
specialCharacters,
|
||||
ignoreCase,
|
||||
}: Pick<CommonOptions, 'specialCharacters' | 'ignoreCase'>): (
|
||||
value: string,
|
||||
) => string
|
||||
Generated
Vendored
+51
@@ -0,0 +1,51 @@
|
||||
import { UnreachableCaseError } from '../unreachable-case-error.js'
|
||||
/**
|
||||
* Creates a function that formats strings for comparison.
|
||||
*
|
||||
* Applies transformations based on the provided options:
|
||||
*
|
||||
* - Case normalization (lowercase if ignoreCase is true)
|
||||
* - Special character handling (keep, trim, or remove)
|
||||
* - Whitespace removal (always applied).
|
||||
*
|
||||
* @param params - Parameters for string formatting.
|
||||
* @param params.ignoreCase - Whether to convert strings to lowercase.
|
||||
* @param params.specialCharacters - How to handle special characters:
|
||||
*
|
||||
* - 'keep': Keep all characters as-is
|
||||
* - 'trim': Remove leading special characters
|
||||
* - 'remove': Remove all special characters.
|
||||
*
|
||||
* @returns Function that formats a string for comparison.
|
||||
* @throws {UnreachableCaseError} If an unknown special characters option is
|
||||
* specified.
|
||||
*/
|
||||
function buildStringFormatter({ specialCharacters, ignoreCase }) {
|
||||
return value => {
|
||||
let valueToCompare = value
|
||||
if (ignoreCase) {
|
||||
valueToCompare = valueToCompare.toLowerCase()
|
||||
}
|
||||
switch (specialCharacters) {
|
||||
case 'remove':
|
||||
valueToCompare = valueToCompare.replaceAll(
|
||||
/[^a-z\u{C0}-\u{24F}\u{1E00}-\u{1EFF}]+/giu,
|
||||
'',
|
||||
)
|
||||
break
|
||||
case 'trim':
|
||||
valueToCompare = valueToCompare.replaceAll(
|
||||
/^[^a-z\u{C0}-\u{24F}\u{1E00}-\u{1EFF}]+/giu,
|
||||
'',
|
||||
)
|
||||
break
|
||||
case 'keep':
|
||||
break
|
||||
/* v8 ignore next 2 -- @preserve Exhaustive guard. */
|
||||
default:
|
||||
throw new UnreachableCaseError(specialCharacters)
|
||||
}
|
||||
return valueToCompare.replaceAll(/\s/gu, '')
|
||||
}
|
||||
}
|
||||
export { buildStringFormatter }
|
||||
Generated
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
import { Comparator } from './default-comparator-by-options-computer.js'
|
||||
import { AllCommonOptions } from '../../types/all-common-options.js'
|
||||
import { SortingNode } from '../../types/sorting-node.js'
|
||||
export declare function buildSubgroupOrderComparator({
|
||||
groups,
|
||||
order,
|
||||
}: Pick<
|
||||
AllCommonOptions<string, unknown, unknown>,
|
||||
'groups' | 'order'
|
||||
>): Comparator<SortingNode>
|
||||
Generated
Vendored
+52
@@ -0,0 +1,52 @@
|
||||
import { isGroupWithOverridesOption } from '../is-group-with-overrides-option.js'
|
||||
import { isNewlinesBetweenOption } from '../is-newlines-between-option.js'
|
||||
import { UnreachableCaseError } from '../unreachable-case-error.js'
|
||||
import { computeOrderedValue } from './compute-ordered-value.js'
|
||||
function buildSubgroupOrderComparator({ groups, order }) {
|
||||
return (a, b) => {
|
||||
let subgroupContainingA = computeSubgroupContainingNode(a, groups)
|
||||
let subgroupContainingB = computeSubgroupContainingNode(b, groups)
|
||||
if (
|
||||
!subgroupContainingA ||
|
||||
!subgroupContainingB ||
|
||||
subgroupContainingA !== subgroupContainingB
|
||||
) {
|
||||
return 0
|
||||
}
|
||||
return computeOrderedValue(
|
||||
subgroupContainingA.indexOf(a.group) -
|
||||
subgroupContainingB.indexOf(b.group),
|
||||
order,
|
||||
)
|
||||
}
|
||||
}
|
||||
function computeSubgroupContainingNode(sortingNode, groups) {
|
||||
for (let group of groups) {
|
||||
if (isNewlinesBetweenOption(group)) {
|
||||
continue
|
||||
}
|
||||
if (typeof group === 'string' || Array.isArray(group)) {
|
||||
if (doesStringSubgroupContainsNode(sortingNode, group)) {
|
||||
return group
|
||||
}
|
||||
continue
|
||||
}
|
||||
/* v8 ignore else -- @preserve Exhaustive guard for unsupported group option. */
|
||||
if (isGroupWithOverridesOption(group)) {
|
||||
if (doesStringSubgroupContainsNode(sortingNode, group.group)) {
|
||||
return group.group
|
||||
}
|
||||
continue
|
||||
}
|
||||
/* v8 ignore next -- @preserve Exhaustive guard for unsupported group option. */
|
||||
throw new UnreachableCaseError(group)
|
||||
}
|
||||
return null
|
||||
}
|
||||
function doesStringSubgroupContainsNode(sortingNode, subgroup) {
|
||||
if (typeof subgroup === 'string') {
|
||||
return false
|
||||
}
|
||||
return subgroup.includes(sortingNode.group)
|
||||
}
|
||||
export { buildSubgroupOrderComparator }
|
||||
Generated
Vendored
+29
@@ -0,0 +1,29 @@
|
||||
import { CommonOptions } from '../../types/common-options.js'
|
||||
/**
|
||||
* Compares two strings alphabetically using locale-aware comparison.
|
||||
*
|
||||
* Applies string formatting based on options (case sensitivity, special
|
||||
* characters handling) before performing the comparison.
|
||||
*
|
||||
* @param a - The first string to compare.
|
||||
* @param b - The second string to compare.
|
||||
* @param options - Comparison options.
|
||||
* @param options.specialCharacters - How to handle special characters.
|
||||
* @param options.ignoreCase - Whether to ignore case differences.
|
||||
* @param options.locales - The locale(s) to use for comparison.
|
||||
* @param options.order - The order direction ('asc' or 'desc').
|
||||
* @returns A negative number if a < b, positive if a > b, or 0 if equal.
|
||||
*/
|
||||
export declare function compareAlphabetically(
|
||||
a: string,
|
||||
b: string,
|
||||
{
|
||||
specialCharacters,
|
||||
ignoreCase,
|
||||
locales,
|
||||
order,
|
||||
}: Pick<
|
||||
CommonOptions,
|
||||
'specialCharacters' | 'ignoreCase' | 'locales' | 'order'
|
||||
>,
|
||||
): number
|
||||
Generated
Vendored
+32
@@ -0,0 +1,32 @@
|
||||
import { computeOrderedValue } from './compute-ordered-value.js'
|
||||
import { buildStringFormatter } from './build-string-formatter.js'
|
||||
/**
|
||||
* Compares two strings alphabetically using locale-aware comparison.
|
||||
*
|
||||
* Applies string formatting based on options (case sensitivity, special
|
||||
* characters handling) before performing the comparison.
|
||||
*
|
||||
* @param a - The first string to compare.
|
||||
* @param b - The second string to compare.
|
||||
* @param options - Comparison options.
|
||||
* @param options.specialCharacters - How to handle special characters.
|
||||
* @param options.ignoreCase - Whether to ignore case differences.
|
||||
* @param options.locales - The locale(s) to use for comparison.
|
||||
* @param options.order - The order direction ('asc' or 'desc').
|
||||
* @returns A negative number if a < b, positive if a > b, or 0 if equal.
|
||||
*/
|
||||
function compareAlphabetically(
|
||||
a,
|
||||
b,
|
||||
{ specialCharacters, ignoreCase, locales, order },
|
||||
) {
|
||||
let formatString = buildStringFormatter({
|
||||
specialCharacters,
|
||||
ignoreCase,
|
||||
})
|
||||
return computeOrderedValue(
|
||||
formatString(a).localeCompare(formatString(b), locales),
|
||||
order,
|
||||
)
|
||||
}
|
||||
export { compareAlphabetically }
|
||||
Generated
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
import { CommonOptions } from '../../types/common-options.js'
|
||||
export declare function compareByCustomSort(
|
||||
a: string,
|
||||
b: string,
|
||||
{
|
||||
specialCharacters,
|
||||
ignoreCase,
|
||||
alphabet,
|
||||
order,
|
||||
}: Pick<
|
||||
CommonOptions,
|
||||
'specialCharacters' | 'ignoreCase' | 'alphabet' | 'order'
|
||||
>,
|
||||
): number
|
||||
Generated
Vendored
+51
@@ -0,0 +1,51 @@
|
||||
import { computeOrderedValue } from './compute-ordered-value.js'
|
||||
import { buildStringFormatter } from './build-string-formatter.js'
|
||||
import { convertBooleanToSign } from '../convert-boolean-to-sign.js'
|
||||
/**
|
||||
* Cache for pre-computed character index maps to avoid recalculating for the
|
||||
* same custom alphabets across multiple comparisons.
|
||||
*/
|
||||
var alphabetCache = /* @__PURE__ */ new Map()
|
||||
function compareByCustomSort(
|
||||
a,
|
||||
b,
|
||||
{ specialCharacters, ignoreCase, alphabet, order },
|
||||
) {
|
||||
let formatString = buildStringFormatter({
|
||||
specialCharacters,
|
||||
ignoreCase,
|
||||
})
|
||||
let indexByCharacters = alphabetCache.get(alphabet)
|
||||
if (!indexByCharacters) {
|
||||
indexByCharacters = /* @__PURE__ */ new Map()
|
||||
for (let [index, character] of [...alphabet].entries()) {
|
||||
indexByCharacters.set(character, index)
|
||||
}
|
||||
alphabetCache.set(alphabet, indexByCharacters)
|
||||
}
|
||||
let aValue = formatString(a)
|
||||
let bValue = formatString(b)
|
||||
let minLength = Math.min(aValue.length, bValue.length)
|
||||
for (let i = 0; i < minLength; i++) {
|
||||
let aCharacter = aValue[i]
|
||||
let bCharacter = bValue[i]
|
||||
let indexOfA = indexByCharacters.get(aCharacter)
|
||||
let indexOfB = indexByCharacters.get(bCharacter)
|
||||
indexOfA ??= Infinity
|
||||
indexOfB ??= Infinity
|
||||
if (indexOfA !== indexOfB) {
|
||||
return computeOrderedValue(
|
||||
convertBooleanToSign(indexOfA - indexOfB > 0),
|
||||
order,
|
||||
)
|
||||
}
|
||||
}
|
||||
if (aValue.length === bValue.length) {
|
||||
return 0
|
||||
}
|
||||
return computeOrderedValue(
|
||||
convertBooleanToSign(aValue.length - bValue.length > 0),
|
||||
order,
|
||||
)
|
||||
}
|
||||
export { compareByCustomSort }
|
||||
Generated
Vendored
+30
@@ -0,0 +1,30 @@
|
||||
import { CommonOptions } from '../../types/common-options.js'
|
||||
/**
|
||||
* Compares two strings using natural sort order.
|
||||
*
|
||||
* Natural sorting handles embedded numbers intelligently, so "item2" comes
|
||||
* before "item10". Applies string formatting based on options before performing
|
||||
* the comparison.
|
||||
*
|
||||
* @param a - The first string to compare.
|
||||
* @param b - The second string to compare.
|
||||
* @param options - Comparison options.
|
||||
* @param options.specialCharacters - How to handle special characters.
|
||||
* @param options.ignoreCase - Whether to ignore case differences.
|
||||
* @param options.locales - The locale(s) to use for comparison.
|
||||
* @param options.order - The order direction ('asc' or 'desc').
|
||||
* @returns A negative number if a < b, positive if a > b, or 0 if equal.
|
||||
*/
|
||||
export declare function compareNaturally(
|
||||
a: string,
|
||||
b: string,
|
||||
{
|
||||
specialCharacters,
|
||||
ignoreCase,
|
||||
locales,
|
||||
order,
|
||||
}: Pick<
|
||||
CommonOptions,
|
||||
'specialCharacters' | 'ignoreCase' | 'locales' | 'order'
|
||||
>,
|
||||
): number
|
||||
Generated
Vendored
+35
@@ -0,0 +1,35 @@
|
||||
import { computeOrderedValue } from './compute-ordered-value.js'
|
||||
import { buildStringFormatter } from './build-string-formatter.js'
|
||||
import { compare } from 'natural-orderby'
|
||||
/**
|
||||
* Compares two strings using natural sort order.
|
||||
*
|
||||
* Natural sorting handles embedded numbers intelligently, so "item2" comes
|
||||
* before "item10". Applies string formatting based on options before performing
|
||||
* the comparison.
|
||||
*
|
||||
* @param a - The first string to compare.
|
||||
* @param b - The second string to compare.
|
||||
* @param options - Comparison options.
|
||||
* @param options.specialCharacters - How to handle special characters.
|
||||
* @param options.ignoreCase - Whether to ignore case differences.
|
||||
* @param options.locales - The locale(s) to use for comparison.
|
||||
* @param options.order - The order direction ('asc' or 'desc').
|
||||
* @returns A negative number if a < b, positive if a > b, or 0 if equal.
|
||||
*/
|
||||
function compareNaturally(
|
||||
a,
|
||||
b,
|
||||
{ specialCharacters, ignoreCase, locales, order },
|
||||
) {
|
||||
let naturalCompare = compare({ locale: locales.toString() })
|
||||
let formatString = buildStringFormatter({
|
||||
specialCharacters,
|
||||
ignoreCase,
|
||||
})
|
||||
return computeOrderedValue(
|
||||
naturalCompare(formatString(a), formatString(b)),
|
||||
order,
|
||||
)
|
||||
}
|
||||
export { compareNaturally }
|
||||
Generated
Vendored
+25
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
ComparatorByOptionsComputer,
|
||||
Comparator,
|
||||
} from './default-comparator-by-options-computer.js'
|
||||
import { CommonOptions } from '../../types/common-options.js'
|
||||
import { SortingNode } from '../../types/sorting-node.js'
|
||||
/**
|
||||
* Computes the array of comparators to use for sorting based on options.
|
||||
*
|
||||
* Returns an array containing the main comparator and a fallback comparator. If
|
||||
* the main comparator is the unsorted comparator, returns an empty array since
|
||||
* no sorting should be performed.
|
||||
*
|
||||
* @param comparatorByOptionsComputer - Function that creates a comparator from
|
||||
* options.
|
||||
* @param options - The sorting options including fallback sort configuration.
|
||||
* @returns An array of comparators, or empty array if sorting is disabled.
|
||||
*/
|
||||
export declare function computeComparators<
|
||||
Options extends Pick<CommonOptions, 'fallbackSort'>,
|
||||
T extends SortingNode,
|
||||
>(
|
||||
comparatorByOptionsComputer: ComparatorByOptionsComputer<Options, T>,
|
||||
options: Options,
|
||||
): Comparator<T>[]
|
||||
Generated
Vendored
+27
@@ -0,0 +1,27 @@
|
||||
import { unsortedComparator } from './unsorted-comparator.js'
|
||||
/**
|
||||
* Computes the array of comparators to use for sorting based on options.
|
||||
*
|
||||
* Returns an array containing the main comparator and a fallback comparator. If
|
||||
* the main comparator is the unsorted comparator, returns an empty array since
|
||||
* no sorting should be performed.
|
||||
*
|
||||
* @param comparatorByOptionsComputer - Function that creates a comparator from
|
||||
* options.
|
||||
* @param options - The sorting options including fallback sort configuration.
|
||||
* @returns An array of comparators, or empty array if sorting is disabled.
|
||||
*/
|
||||
function computeComparators(comparatorByOptionsComputer, options) {
|
||||
let mainComparator = comparatorByOptionsComputer(options)
|
||||
if (mainComparator === unsortedComparator) {
|
||||
return []
|
||||
}
|
||||
return [
|
||||
mainComparator,
|
||||
comparatorByOptionsComputer({
|
||||
...options,
|
||||
...options.fallbackSort,
|
||||
}),
|
||||
]
|
||||
}
|
||||
export { computeComparators }
|
||||
Generated
Vendored
+15
@@ -0,0 +1,15 @@
|
||||
import { CommonOptions } from '../../types/common-options.js'
|
||||
/**
|
||||
* Adjusts a comparison result value based on the specified sort order.
|
||||
*
|
||||
* For ascending order, returns the value unchanged. For descending order,
|
||||
* negates the value to reverse the sort direction.
|
||||
*
|
||||
* @param value - The comparison result value to adjust.
|
||||
* @param order - The order direction ('asc' or 'desc').
|
||||
* @returns The adjusted comparison value.
|
||||
*/
|
||||
export declare function computeOrderedValue(
|
||||
value: number,
|
||||
order: CommonOptions['order'],
|
||||
): number
|
||||
Generated
Vendored
+23
@@ -0,0 +1,23 @@
|
||||
import { UnreachableCaseError } from '../unreachable-case-error.js'
|
||||
/**
|
||||
* Adjusts a comparison result value based on the specified sort order.
|
||||
*
|
||||
* For ascending order, returns the value unchanged. For descending order,
|
||||
* negates the value to reverse the sort direction.
|
||||
*
|
||||
* @param value - The comparison result value to adjust.
|
||||
* @param order - The order direction ('asc' or 'desc').
|
||||
* @returns The adjusted comparison value.
|
||||
*/
|
||||
function computeOrderedValue(value, order) {
|
||||
switch (order) {
|
||||
case 'desc':
|
||||
return -value
|
||||
case 'asc':
|
||||
return value
|
||||
/* v8 ignore next 2 -- @preserve Exhaustive guard. */
|
||||
default:
|
||||
throw new UnreachableCaseError(order)
|
||||
}
|
||||
}
|
||||
export { computeOrderedValue }
|
||||
Generated
Vendored
+19
@@ -0,0 +1,19 @@
|
||||
import { CommonOptions, TypeOption } from '../../types/common-options.js'
|
||||
import { GroupsOptions } from '../../types/common-groups-options.js'
|
||||
import { SortingNode } from '../../types/sorting-node.js'
|
||||
export type ComparatorByOptionsComputer<S, T extends SortingNode> = (
|
||||
options: S,
|
||||
) => Comparator<T>
|
||||
export type Comparator<T extends SortingNode> = (a: T, b: T) => number
|
||||
type Options = Pick<
|
||||
CommonOptions<TypeOption>,
|
||||
'specialCharacters' | 'ignoreCase' | 'alphabet' | 'locales' | 'order' | 'type'
|
||||
> &
|
||||
Pick<CommonOptions, 'fallbackSort'> & {
|
||||
groups?: GroupsOptions
|
||||
}
|
||||
export declare let defaultComparatorByOptionsComputer: ComparatorByOptionsComputer<
|
||||
Options,
|
||||
SortingNode
|
||||
>
|
||||
export {}
|
||||
Generated
Vendored
+33
@@ -0,0 +1,33 @@
|
||||
import { UnreachableCaseError } from '../unreachable-case-error.js'
|
||||
import { buildSubgroupOrderComparator } from './build-subgroup-order-comparator.js'
|
||||
import { buildLineLengthComparator } from './build-line-length-comparator.js'
|
||||
import { compareAlphabetically } from './compare-alphabetically.js'
|
||||
import { compareByCustomSort } from './compare-by-custom-sort.js'
|
||||
import { unsortedComparator } from './unsorted-comparator.js'
|
||||
import { compareNaturally } from './compare-naturally.js'
|
||||
var defaultComparatorByOptionsComputer = options => {
|
||||
switch (options.type) {
|
||||
case 'subgroup-order':
|
||||
if (!options.groups) {
|
||||
return unsortedComparator
|
||||
}
|
||||
return buildSubgroupOrderComparator({
|
||||
...options,
|
||||
groups: options.groups,
|
||||
})
|
||||
case 'alphabetical':
|
||||
return (a, b) => compareAlphabetically(a.name, b.name, options)
|
||||
case 'line-length':
|
||||
return buildLineLengthComparator(options)
|
||||
case 'unsorted':
|
||||
return unsortedComparator
|
||||
case 'natural':
|
||||
return (a, b) => compareNaturally(a.name, b.name, options)
|
||||
case 'custom':
|
||||
return (a, b) => compareByCustomSort(a.name, b.name, options)
|
||||
/* v8 ignore next 2 -- @preserve Exhaustive guard. */
|
||||
default:
|
||||
throw new UnreachableCaseError(options.type)
|
||||
}
|
||||
}
|
||||
export { defaultComparatorByOptionsComputer }
|
||||
Generated
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
import { Comparator } from './default-comparator-by-options-computer.js'
|
||||
import { SortingNode } from '../../types/sorting-node.js'
|
||||
export declare let unsortedComparator: Comparator<SortingNode>
|
||||
Generated
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
var unsortedComparator = () => 0
|
||||
export { unsortedComparator }
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
import { Settings } from './get-settings.js'
|
||||
/**
|
||||
* Merges configuration options with settings and defaults in priority order.
|
||||
*
|
||||
* Combines three levels of configuration with increasing priority:
|
||||
*
|
||||
* 1. Default values (lowest priority)
|
||||
* 2. Global settings from ESLint configuration.
|
||||
* 3. Rule-specific options (highest priority).
|
||||
*
|
||||
* This ensures that user-provided options always override settings, and
|
||||
* settings always override defaults.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* const finalOptions = complete(
|
||||
* { type: 'natural' }, // User options (highest priority)
|
||||
* { order: 'asc' }, // Global settings
|
||||
* { type: 'alphabetical', order: 'desc' }, // Defaults (lowest priority)
|
||||
* )
|
||||
* // Returns: { type: 'natural', order: 'asc' }
|
||||
* ```
|
||||
*
|
||||
* @template T - Type of the configuration object.
|
||||
* @param options - Rule-specific options provided by the user (highest
|
||||
* priority).
|
||||
* @param settings - Global settings from ESLint configuration.
|
||||
* @param defaults - Default values for the configuration (lowest priority).
|
||||
* @returns Merged configuration object with all three levels combined.
|
||||
*/
|
||||
export declare function complete<T extends Record<string, unknown>>(
|
||||
options?: Partial<T>,
|
||||
settings?: Settings,
|
||||
defaults?: T,
|
||||
): T
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Merges configuration options with settings and defaults in priority order.
|
||||
*
|
||||
* Combines three levels of configuration with increasing priority:
|
||||
*
|
||||
* 1. Default values (lowest priority)
|
||||
* 2. Global settings from ESLint configuration.
|
||||
* 3. Rule-specific options (highest priority).
|
||||
*
|
||||
* This ensures that user-provided options always override settings, and
|
||||
* settings always override defaults.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* const finalOptions = complete(
|
||||
* { type: 'natural' }, // User options (highest priority)
|
||||
* { order: 'asc' }, // Global settings
|
||||
* { type: 'alphabetical', order: 'desc' }, // Defaults (lowest priority)
|
||||
* )
|
||||
* // Returns: { type: 'natural', order: 'asc' }
|
||||
* ```
|
||||
*
|
||||
* @template T - Type of the configuration object.
|
||||
* @param options - Rule-specific options provided by the user (highest
|
||||
* priority).
|
||||
* @param settings - Global settings from ESLint configuration.
|
||||
* @param defaults - Default values for the configuration (lowest priority).
|
||||
* @returns Merged configuration object with all three levels combined.
|
||||
*/
|
||||
function complete(options = {}, settings = {}, defaults = {}) {
|
||||
return {
|
||||
...defaults,
|
||||
...settings,
|
||||
...options,
|
||||
}
|
||||
}
|
||||
export { complete }
|
||||
Generated
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
import { TSESLint } from '@typescript-eslint/utils'
|
||||
import { TSESTree } from '@typescript-eslint/types'
|
||||
/**
|
||||
* Recursively computes all scope references deeply for a given node.
|
||||
*
|
||||
* @param node - The AST node.
|
||||
* @param sourceCode - The source code object.
|
||||
* @returns The list of scope references.
|
||||
*/
|
||||
export declare function computeDeepScopeReferences(
|
||||
node: TSESTree.Node,
|
||||
sourceCode: TSESLint.SourceCode,
|
||||
): TSESLint.Scope.Reference[]
|
||||
Generated
Vendored
+17
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Recursively computes all scope references deeply for a given node.
|
||||
*
|
||||
* @param node - The AST node.
|
||||
* @param sourceCode - The source code object.
|
||||
* @returns The list of scope references.
|
||||
*/
|
||||
function computeDeepScopeReferences(node, sourceCode) {
|
||||
return computeScopeReference(sourceCode.getScope(node))
|
||||
function computeScopeReference(scope) {
|
||||
return [
|
||||
...scope.references,
|
||||
...scope.childScopes.flatMap(computeScopeReference),
|
||||
]
|
||||
}
|
||||
}
|
||||
export { computeDeepScopeReferences }
|
||||
Generated
Vendored
+42
@@ -0,0 +1,42 @@
|
||||
import { TSESTree } from '@typescript-eslint/types'
|
||||
import { TSESLint } from '@typescript-eslint/utils'
|
||||
import { SortingNodeWithDependencies } from './sort-nodes-by-dependencies.js'
|
||||
export type ShouldIgnoreIdentifierComputer<T> = (parameters: {
|
||||
identifier: TSESTree.JSXIdentifier | TSESTree.Identifier
|
||||
referencingSortingNode: T
|
||||
}) => boolean
|
||||
export type AdditionalIdentifierDependenciesComputer<T> = (parameters: {
|
||||
reference: TSESLint.Scope.Reference
|
||||
referencingSortingNode: T
|
||||
}) => T[]
|
||||
export type ShouldIgnoreSortingNodeComputer<T> = (sortingNode: T) => boolean
|
||||
/**
|
||||
* Compute the list of dependencies for each sorting node.
|
||||
*
|
||||
* @param params - The parameters object.
|
||||
* @param params.additionalIdentifierDependenciesComputer - A function to
|
||||
* compute additional dependencies for an identifier.
|
||||
* @param params.shouldIgnoreSortingNodeComputer - A function to determine if a
|
||||
* sorting node should be ignored.
|
||||
* @param params.shouldIgnoreIdentifierComputer - A function to determine if an
|
||||
* identifier should be ignored.
|
||||
* @param params.sortingNodes - The sorting nodes to compute dependencies for.
|
||||
* @param params.sourceCode - The source code object.
|
||||
* @returns A map of sorting nodes to their dependencies.
|
||||
*/
|
||||
export declare function computeDependenciesBySortingNode<
|
||||
Node extends TSESTree.Node,
|
||||
T extends Pick<SortingNodeWithDependencies<Node>, 'dependencyNames' | 'node'>,
|
||||
>({
|
||||
additionalIdentifierDependenciesComputer,
|
||||
shouldIgnoreSortingNodeComputer,
|
||||
shouldIgnoreIdentifierComputer,
|
||||
sortingNodes,
|
||||
sourceCode,
|
||||
}: {
|
||||
additionalIdentifierDependenciesComputer?: AdditionalIdentifierDependenciesComputer<T>
|
||||
shouldIgnoreSortingNodeComputer?: ShouldIgnoreSortingNodeComputer<T>
|
||||
shouldIgnoreIdentifierComputer?: ShouldIgnoreIdentifierComputer<T>
|
||||
sourceCode: TSESLint.SourceCode
|
||||
sortingNodes: T[]
|
||||
}): Map<T, T[]>
|
||||
frontend/node_modules/eslint-plugin-perfectionist/dist/utils/compute-dependencies-by-sorting-node.js
Generated
Vendored
+102
@@ -0,0 +1,102 @@
|
||||
import { computeDeepScopeReferences } from './compute-deep-scope-references.js'
|
||||
import { rangeContainsRange } from './range-contains-range.js'
|
||||
/**
|
||||
* Compute the list of dependencies for each sorting node.
|
||||
*
|
||||
* @param params - The parameters object.
|
||||
* @param params.additionalIdentifierDependenciesComputer - A function to
|
||||
* compute additional dependencies for an identifier.
|
||||
* @param params.shouldIgnoreSortingNodeComputer - A function to determine if a
|
||||
* sorting node should be ignored.
|
||||
* @param params.shouldIgnoreIdentifierComputer - A function to determine if an
|
||||
* identifier should be ignored.
|
||||
* @param params.sortingNodes - The sorting nodes to compute dependencies for.
|
||||
* @param params.sourceCode - The source code object.
|
||||
* @returns A map of sorting nodes to their dependencies.
|
||||
*/
|
||||
function computeDependenciesBySortingNode({
|
||||
additionalIdentifierDependenciesComputer,
|
||||
shouldIgnoreSortingNodeComputer,
|
||||
shouldIgnoreIdentifierComputer,
|
||||
sortingNodes,
|
||||
sourceCode,
|
||||
}) {
|
||||
let returnValue = /* @__PURE__ */ new Map()
|
||||
let references = sortingNodes.flatMap(sortingNode =>
|
||||
computeDeepScopeReferences(sortingNode.node, sourceCode),
|
||||
)
|
||||
for (let reference of new Set(references)) {
|
||||
let { identifier, resolved } = reference
|
||||
if (!resolved) {
|
||||
continue
|
||||
}
|
||||
let referencingSortingNode = findSortingNodeContainingIdentifier(
|
||||
sortingNodes,
|
||||
identifier,
|
||||
)
|
||||
if (!referencingSortingNode) {
|
||||
continue
|
||||
}
|
||||
if (shouldIgnoreSortingNodeComputer?.(referencingSortingNode)) {
|
||||
continue
|
||||
}
|
||||
let referencedNodes = returnValue.get(referencingSortingNode) ?? []
|
||||
returnValue.set(referencingSortingNode, referencedNodes)
|
||||
referencedNodes.push(
|
||||
...computeMainIdentifierDependencies({
|
||||
shouldIgnoreSortingNodeComputer,
|
||||
shouldIgnoreIdentifierComputer,
|
||||
referencingSortingNode,
|
||||
sortingNodes,
|
||||
identifier,
|
||||
resolved,
|
||||
}),
|
||||
...(additionalIdentifierDependenciesComputer?.({
|
||||
referencingSortingNode,
|
||||
reference,
|
||||
}) ?? []),
|
||||
)
|
||||
}
|
||||
return returnValue
|
||||
}
|
||||
function computeMainIdentifierDependencies({
|
||||
shouldIgnoreSortingNodeComputer,
|
||||
shouldIgnoreIdentifierComputer,
|
||||
referencingSortingNode,
|
||||
sortingNodes,
|
||||
identifier,
|
||||
resolved,
|
||||
}) {
|
||||
if (
|
||||
shouldIgnoreIdentifierComputer?.({
|
||||
referencingSortingNode,
|
||||
identifier,
|
||||
})
|
||||
) {
|
||||
return []
|
||||
}
|
||||
let [firstIdentifier] = resolved.identifiers
|
||||
if (!firstIdentifier) {
|
||||
return []
|
||||
}
|
||||
let referencedSortingNode = findSortingNodeContainingIdentifier(
|
||||
sortingNodes,
|
||||
firstIdentifier,
|
||||
)
|
||||
if (!referencedSortingNode) {
|
||||
return []
|
||||
}
|
||||
if (shouldIgnoreSortingNodeComputer?.(referencedSortingNode)) {
|
||||
return []
|
||||
}
|
||||
if (referencedSortingNode === referencingSortingNode) {
|
||||
return []
|
||||
}
|
||||
return [referencedSortingNode]
|
||||
}
|
||||
function findSortingNodeContainingIdentifier(sortingNodes, identifier) {
|
||||
return sortingNodes.find(sortingNode =>
|
||||
rangeContainsRange(sortingNode.node.range, identifier.range),
|
||||
)
|
||||
}
|
||||
export { computeDependenciesBySortingNode }
|
||||
Generated
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
import { TSESTree } from '@typescript-eslint/types'
|
||||
import { TSESLint } from '@typescript-eslint/utils'
|
||||
import { SortingNodeWithDependencies } from './sort-nodes-by-dependencies.js'
|
||||
export declare function computeDependenciesOutsideFunctionsBySortingNode<
|
||||
Node extends TSESTree.Node,
|
||||
T extends Pick<SortingNodeWithDependencies<Node>, 'dependencyNames' | 'node'>,
|
||||
>({
|
||||
sortingNodes,
|
||||
sourceCode,
|
||||
}: {
|
||||
sourceCode: TSESLint.SourceCode
|
||||
sortingNodes: T[]
|
||||
}): Map<T, T[]>
|
||||
Generated
Vendored
+29
@@ -0,0 +1,29 @@
|
||||
import { computeDependenciesBySortingNode } from './compute-dependencies-by-sorting-node.js'
|
||||
import { computeParentNodesWithTypes } from './compute-parent-nodes-with-types.js'
|
||||
import { AST_NODE_TYPES } from '@typescript-eslint/utils'
|
||||
function computeDependenciesOutsideFunctionsBySortingNode({
|
||||
sortingNodes,
|
||||
sourceCode,
|
||||
}) {
|
||||
return computeDependenciesBySortingNode({
|
||||
shouldIgnoreIdentifierComputer: buildShouldIgnoreIdentifierComputer(),
|
||||
sortingNodes,
|
||||
sourceCode,
|
||||
})
|
||||
function buildShouldIgnoreIdentifierComputer() {
|
||||
return ({ referencingSortingNode, identifier }) => {
|
||||
return (
|
||||
computeParentNodesWithTypes({
|
||||
allowedTypes: [
|
||||
AST_NODE_TYPES.FunctionExpression,
|
||||
AST_NODE_TYPES.ArrowFunctionExpression,
|
||||
],
|
||||
maxParent: referencingSortingNode.node,
|
||||
consecutiveOnly: false,
|
||||
node: identifier,
|
||||
}).length > 0
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
export { computeDependenciesOutsideFunctionsBySortingNode }
|
||||
Generated
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
import { GroupsOptions } from '../types/common-groups-options.js'
|
||||
/**
|
||||
* Computes the name of a group based on the provided group object.
|
||||
*
|
||||
* @param group - The group object.
|
||||
* @returns A string if:
|
||||
*
|
||||
* - The group is a string.
|
||||
* - The group is a commentAbove option with a string group.
|
||||
*/
|
||||
export declare function computeGroupName(
|
||||
group: GroupsOptions[number],
|
||||
): string | null
|
||||
Generated
Vendored
+33
@@ -0,0 +1,33 @@
|
||||
import { isGroupWithOverridesOption } from './is-group-with-overrides-option.js'
|
||||
import { isNewlinesBetweenOption } from './is-newlines-between-option.js'
|
||||
import { UnreachableCaseError } from './unreachable-case-error.js'
|
||||
/**
|
||||
* Computes the name of a group based on the provided group object.
|
||||
*
|
||||
* @param group - The group object.
|
||||
* @returns A string if:
|
||||
*
|
||||
* - The group is a string.
|
||||
* - The group is a commentAbove option with a string group.
|
||||
*/
|
||||
function computeGroupName(group) {
|
||||
if (typeof group === 'string' || Array.isArray(group)) {
|
||||
return computeStringGroupName(group)
|
||||
}
|
||||
if (isGroupWithOverridesOption(group)) {
|
||||
return computeStringGroupName(group.group)
|
||||
}
|
||||
/* v8 ignore else -- @preserve Exhaustive guard for unsupported group option. */
|
||||
if (isNewlinesBetweenOption(group)) {
|
||||
return null
|
||||
}
|
||||
/* v8 ignore next -- @preserve Exhaustive guard for unsupported group option. */
|
||||
throw new UnreachableCaseError(group)
|
||||
}
|
||||
function computeStringGroupName(group) {
|
||||
if (typeof group === 'string') {
|
||||
return group
|
||||
}
|
||||
return null
|
||||
}
|
||||
export { computeGroupName }
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
import {
|
||||
CommonGroupsOptions,
|
||||
AnyOfCustomGroup,
|
||||
} from '../types/common-groups-options.js'
|
||||
/**
|
||||
* Parameters for computing the group of an element.
|
||||
*
|
||||
* @template CustomGroupMatchOptions - Type of custom group match options.
|
||||
*/
|
||||
interface ComputeGroupParameters<CustomGroupMatchOptions> {
|
||||
/**
|
||||
* Configuration options for grouping.
|
||||
*/
|
||||
options: Pick<
|
||||
CommonGroupsOptions<string, unknown, CustomGroupMatchOptions>,
|
||||
'customGroups' | 'groups'
|
||||
>
|
||||
/**
|
||||
* Function to test if an element matches a custom group. Takes a custom
|
||||
* group configuration and returns true if the element matches.
|
||||
*/
|
||||
customGroupMatcher: CustomGroupMatcher<CustomGroupMatchOptions>
|
||||
/**
|
||||
* List of predefined groups that the element belongs to. These are checked
|
||||
* after custom groups as a fallback.
|
||||
*/
|
||||
predefinedGroups: string[]
|
||||
}
|
||||
type CustomGroupMatcher<MatchOptions> = (
|
||||
customGroup: AnyOfCustomGroup<MatchOptions> | Partial<MatchOptions>,
|
||||
) => boolean
|
||||
/**
|
||||
* Determines which group an element belongs to based on custom and predefined
|
||||
* groups.
|
||||
*
|
||||
* The function checks groups in the following priority order:
|
||||
*
|
||||
* 1. Custom groups (if defined) - checked first, highest priority
|
||||
* 2. Predefined groups - checked as fallback
|
||||
* 3. Returns 'unknown' if no matching group is found.
|
||||
*
|
||||
* Only groups that exist in options.groups are considered valid.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* const group = computeGroup({
|
||||
* options: {
|
||||
* groups: ['react', 'external', 'internal'],
|
||||
* customGroups: [{ groupName: 'react', anyOf: ['react', 'react-*'] }],
|
||||
* },
|
||||
* customGroupMatcher: customGroup => customGroup.anyOf.includes('react'),
|
||||
* predefinedGroups: ['external'],
|
||||
* name: 'react-dom',
|
||||
* })
|
||||
* // Returns: 'react'
|
||||
* ```
|
||||
*
|
||||
* @template CustomGroupMatchOptions - Type of custom group match options.
|
||||
* @param params - Parameters for group computation.
|
||||
* @param params.options - Configuration with available groups and custom
|
||||
* groups.
|
||||
* @param params.customGroupMatcher - Matcher function for custom groups.
|
||||
* @param params.predefinedGroups - Fallback predefined groups to check.
|
||||
* @returns The matched group name or 'unknown' if no group matches.
|
||||
*/
|
||||
export declare function computeGroup<CustomGroupMatchOptions>({
|
||||
customGroupMatcher,
|
||||
predefinedGroups,
|
||||
options,
|
||||
}: ComputeGroupParameters<CustomGroupMatchOptions>): 'unknown' | string
|
||||
export {}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
import { computeGroupsNames } from './compute-groups-names.js'
|
||||
/**
|
||||
* Determines which group an element belongs to based on custom and predefined
|
||||
* groups.
|
||||
*
|
||||
* The function checks groups in the following priority order:
|
||||
*
|
||||
* 1. Custom groups (if defined) - checked first, highest priority
|
||||
* 2. Predefined groups - checked as fallback
|
||||
* 3. Returns 'unknown' if no matching group is found.
|
||||
*
|
||||
* Only groups that exist in options.groups are considered valid.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* const group = computeGroup({
|
||||
* options: {
|
||||
* groups: ['react', 'external', 'internal'],
|
||||
* customGroups: [{ groupName: 'react', anyOf: ['react', 'react-*'] }],
|
||||
* },
|
||||
* customGroupMatcher: customGroup => customGroup.anyOf.includes('react'),
|
||||
* predefinedGroups: ['external'],
|
||||
* name: 'react-dom',
|
||||
* })
|
||||
* // Returns: 'react'
|
||||
* ```
|
||||
*
|
||||
* @template CustomGroupMatchOptions - Type of custom group match options.
|
||||
* @param params - Parameters for group computation.
|
||||
* @param params.options - Configuration with available groups and custom
|
||||
* groups.
|
||||
* @param params.customGroupMatcher - Matcher function for custom groups.
|
||||
* @param params.predefinedGroups - Fallback predefined groups to check.
|
||||
* @returns The matched group name or 'unknown' if no group matches.
|
||||
*/
|
||||
function computeGroup({ customGroupMatcher, predefinedGroups, options }) {
|
||||
let groupsSet = new Set(computeGroupsNames(options.groups))
|
||||
return (
|
||||
computeFirstMatchingCustomGroupName(
|
||||
groupsSet,
|
||||
options.customGroups,
|
||||
customGroupMatcher,
|
||||
) ??
|
||||
predefinedGroups.find(group => groupsSet.has(group)) ??
|
||||
'unknown'
|
||||
)
|
||||
}
|
||||
function computeFirstMatchingCustomGroupName(
|
||||
groupsSet,
|
||||
customGroups,
|
||||
customGroupMatcher,
|
||||
) {
|
||||
for (let customGroup of customGroups) {
|
||||
if (
|
||||
customGroupMatcher(customGroup) &&
|
||||
groupsSet.has(customGroup.groupName)
|
||||
) {
|
||||
return customGroup.groupName
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
export { computeGroup }
|
||||
Generated
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
import { GroupsOptions } from '../types/common-groups-options.js'
|
||||
/**
|
||||
* Computes the names of all groups based on the provided `GroupsOptions`.
|
||||
*
|
||||
* @param groups - An array of group options.
|
||||
* @returns An array of computed group names as strings.
|
||||
*/
|
||||
export declare function computeGroupsNames(groups: GroupsOptions): string[]
|
||||
Generated
Vendored
+33
@@ -0,0 +1,33 @@
|
||||
import { isGroupWithOverridesOption } from './is-group-with-overrides-option.js'
|
||||
import { isNewlinesBetweenOption } from './is-newlines-between-option.js'
|
||||
import { UnreachableCaseError } from './unreachable-case-error.js'
|
||||
/**
|
||||
* Computes the names of all groups based on the provided `GroupsOptions`.
|
||||
*
|
||||
* @param groups - An array of group options.
|
||||
* @returns An array of computed group names as strings.
|
||||
*/
|
||||
function computeGroupsNames(groups) {
|
||||
return groups.flatMap(group => computeGroupNames(group))
|
||||
}
|
||||
function computeGroupNames(group) {
|
||||
if (typeof group === 'string' || Array.isArray(group)) {
|
||||
return computeStringGroupNames(group)
|
||||
}
|
||||
if (isGroupWithOverridesOption(group)) {
|
||||
return computeStringGroupNames(group.group)
|
||||
}
|
||||
/* v8 ignore else -- @preserve Exhaustive guard for unsupported group option. */
|
||||
if (isNewlinesBetweenOption(group)) {
|
||||
return []
|
||||
}
|
||||
/* v8 ignore next -- @preserve Exhaustive guard for unsupported group option. */
|
||||
throw new UnreachableCaseError(group)
|
||||
}
|
||||
function computeStringGroupNames(group) {
|
||||
if (typeof group === 'string') {
|
||||
return [group]
|
||||
}
|
||||
return group
|
||||
}
|
||||
export { computeGroupsNames }
|
||||
Generated
Vendored
+39
@@ -0,0 +1,39 @@
|
||||
import { SortingNodeWithDependencies } from './sort-nodes-by-dependencies.js'
|
||||
/**
|
||||
* Detects nodes that are part of circular dependency chains.
|
||||
*
|
||||
* Uses a depth-first search (DFS) algorithm with three-color marking to
|
||||
* identify cycles in the dependency graph. When a cycle is detected, all nodes
|
||||
* in that cycle are added to the result set.
|
||||
*
|
||||
* The algorithm tracks three states for each node:
|
||||
*
|
||||
* - Not visited: Node hasn't been processed yet
|
||||
* - Visiting: Currently in the DFS path (gray in three-color marking)
|
||||
* - Visited: Completely processed (black in three-color marking).
|
||||
*
|
||||
* A cycle is detected when we encounter a node that is already in the
|
||||
* "visiting" state, meaning we've found a back edge in the graph.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* const nodes = [
|
||||
* { name: 'A', dependencies: ['B'], dependencyNames: ['A'] },
|
||||
* { name: 'B', dependencies: ['C'], dependencyNames: ['B'] },
|
||||
* { name: 'C', dependencies: ['A'], dependencyNames: ['C'] },
|
||||
* ]
|
||||
* const circularNodes = computeNodesInCircularDependencies(nodes)
|
||||
* // Returns: Set containing all three nodes (A, B, C)
|
||||
* ```
|
||||
*
|
||||
* @template T - Type of sorting node with dependencies.
|
||||
* @param elements - Array of nodes with dependency information.
|
||||
* @returns Set of nodes that participate in circular dependencies.
|
||||
*/
|
||||
export declare function computeNodesInCircularDependencies<
|
||||
T extends Pick<
|
||||
SortingNodeWithDependencies,
|
||||
'dependencyNames' | 'dependencies'
|
||||
>,
|
||||
>(elements: T[]): Set<T>
|
||||
Generated
Vendored
+85
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* Detects nodes that are part of circular dependency chains.
|
||||
*
|
||||
* Uses a depth-first search (DFS) algorithm with three-color marking to
|
||||
* identify cycles in the dependency graph. When a cycle is detected, all nodes
|
||||
* in that cycle are added to the result set.
|
||||
*
|
||||
* The algorithm tracks three states for each node:
|
||||
*
|
||||
* - Not visited: Node hasn't been processed yet
|
||||
* - Visiting: Currently in the DFS path (gray in three-color marking)
|
||||
* - Visited: Completely processed (black in three-color marking).
|
||||
*
|
||||
* A cycle is detected when we encounter a node that is already in the
|
||||
* "visiting" state, meaning we've found a back edge in the graph.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* const nodes = [
|
||||
* { name: 'A', dependencies: ['B'], dependencyNames: ['A'] },
|
||||
* { name: 'B', dependencies: ['C'], dependencyNames: ['B'] },
|
||||
* { name: 'C', dependencies: ['A'], dependencyNames: ['C'] },
|
||||
* ]
|
||||
* const circularNodes = computeNodesInCircularDependencies(nodes)
|
||||
* // Returns: Set containing all three nodes (A, B, C)
|
||||
* ```
|
||||
*
|
||||
* @template T - Type of sorting node with dependencies.
|
||||
* @param elements - Array of nodes with dependency information.
|
||||
* @returns Set of nodes that participate in circular dependencies.
|
||||
*/
|
||||
function computeNodesInCircularDependencies(elements) {
|
||||
let elementsInCycles = /* @__PURE__ */ new Set()
|
||||
let visitingElements = /* @__PURE__ */ new Set()
|
||||
let visitedElements = /* @__PURE__ */ new Set()
|
||||
/**
|
||||
* Performs depth-first search to detect cycles starting from the given
|
||||
* element.
|
||||
*
|
||||
* Recursively traverses the dependency graph, maintaining a path of the
|
||||
* current traversal. If a node in the current path is encountered again, a
|
||||
* cycle is detected and all nodes in the cycle are marked.
|
||||
*
|
||||
* @param element - Current node being visited.
|
||||
* @param path - Array of nodes in the current DFS path.
|
||||
*/
|
||||
function depthFirstSearch(element, path) {
|
||||
if (visitedElements.has(element)) {
|
||||
return
|
||||
}
|
||||
if (visitingElements.has(element)) {
|
||||
let cycleStartIndex = path.indexOf(element)
|
||||
/* v8 ignore else -- @preserve Visiting path already contains the element when this branch executes. */
|
||||
if (cycleStartIndex !== -1) {
|
||||
for (let cycleElements of path.slice(cycleStartIndex)) {
|
||||
elementsInCycles.add(cycleElements)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
visitingElements.add(element)
|
||||
path.push(element)
|
||||
for (let dependency of element.dependencies) {
|
||||
let dependencyElement = elements
|
||||
.filter(currentElement => currentElement !== element)
|
||||
.find(currentElement =>
|
||||
currentElement.dependencyNames.includes(dependency),
|
||||
)
|
||||
/* v8 ignore next -- @preserve Dependencies are pre-filtered; missing entries are defensive fallback. */
|
||||
if (dependencyElement) {
|
||||
depthFirstSearch(dependencyElement, [...path])
|
||||
}
|
||||
}
|
||||
visitingElements.delete(element)
|
||||
visitedElements.add(element)
|
||||
}
|
||||
for (let element of elements) {
|
||||
if (!visitedElements.has(element)) {
|
||||
depthFirstSearch(element, [])
|
||||
}
|
||||
}
|
||||
return elementsInCycles
|
||||
}
|
||||
export { computeNodesInCircularDependencies }
|
||||
Generated
Vendored
+51
@@ -0,0 +1,51 @@
|
||||
import { CommonGroupsOptions } from '../types/common-groups-options.js'
|
||||
import { CommonOptions } from '../types/common-options.js'
|
||||
type Options = Pick<
|
||||
CommonGroupsOptions<string, unknown, unknown>,
|
||||
'customGroups' | 'groups'
|
||||
> &
|
||||
CommonOptions
|
||||
/**
|
||||
* Retrieves sorting options potentially overridden by a custom group or group
|
||||
* with settings configuration.
|
||||
*
|
||||
* Checks if the group at the specified index is a custom group with its own
|
||||
* sorting configuration. If so, returns the overridden options (type, order,
|
||||
* fallbackSort). Otherwise, returns the original options.
|
||||
*
|
||||
* Custom groups can override:
|
||||
*
|
||||
* - Sort type (e.g., use 'natural' instead of global 'alphabetical')
|
||||
* - Sort order (e.g., use 'desc' instead of global 'asc')
|
||||
* - Fallback sort configuration.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* const options = {
|
||||
* type: 'alphabetical',
|
||||
* order: 'asc',
|
||||
* fallbackSort: { type: 'natural' },
|
||||
* groups: ['custom-group', 'other'],
|
||||
* customGroups: [
|
||||
* {
|
||||
* groupName: 'custom-group',
|
||||
* type: 'natural',
|
||||
* order: 'desc',
|
||||
* },
|
||||
* ],
|
||||
* }
|
||||
* const overridden = computeOverriddenOptionsByGroupIndex(options, 0)
|
||||
* // Returns: { type: 'natural', order: 'desc', fallbackSort: { type: 'natural' } }
|
||||
* ```
|
||||
*
|
||||
* @param options - Combined group and sorting options.
|
||||
* @param groupIndex - Index of the group to check for overrides.
|
||||
* @returns Sorting options, potentially overridden by custom group
|
||||
* configuration.
|
||||
*/
|
||||
export declare function computeOverriddenOptionsByGroupIndex<T extends Options>(
|
||||
options: T,
|
||||
groupIndex: number,
|
||||
): T
|
||||
export {}
|
||||
Generated
Vendored
+86
@@ -0,0 +1,86 @@
|
||||
import { isGroupWithOverridesOption } from './is-group-with-overrides-option.js'
|
||||
import { computeGroupName } from './compute-group-name.js'
|
||||
/**
|
||||
* Retrieves sorting options potentially overridden by a custom group or group
|
||||
* with settings configuration.
|
||||
*
|
||||
* Checks if the group at the specified index is a custom group with its own
|
||||
* sorting configuration. If so, returns the overridden options (type, order,
|
||||
* fallbackSort). Otherwise, returns the original options.
|
||||
*
|
||||
* Custom groups can override:
|
||||
*
|
||||
* - Sort type (e.g., use 'natural' instead of global 'alphabetical')
|
||||
* - Sort order (e.g., use 'desc' instead of global 'asc')
|
||||
* - Fallback sort configuration.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* const options = {
|
||||
* type: 'alphabetical',
|
||||
* order: 'asc',
|
||||
* fallbackSort: { type: 'natural' },
|
||||
* groups: ['custom-group', 'other'],
|
||||
* customGroups: [
|
||||
* {
|
||||
* groupName: 'custom-group',
|
||||
* type: 'natural',
|
||||
* order: 'desc',
|
||||
* },
|
||||
* ],
|
||||
* }
|
||||
* const overridden = computeOverriddenOptionsByGroupIndex(options, 0)
|
||||
* // Returns: { type: 'natural', order: 'desc', fallbackSort: { type: 'natural' } }
|
||||
* ```
|
||||
*
|
||||
* @param options - Combined group and sorting options.
|
||||
* @param groupIndex - Index of the group to check for overrides.
|
||||
* @returns Sorting options, potentially overridden by custom group
|
||||
* configuration.
|
||||
*/
|
||||
function computeOverriddenOptionsByGroupIndex(options, groupIndex) {
|
||||
let { customGroups, groups } = options
|
||||
let matchingGroup = groups[groupIndex]
|
||||
let matchingGroupName = matchingGroup ? computeGroupName(matchingGroup) : null
|
||||
let customGroup = customGroups.find(
|
||||
currentGroup => matchingGroupName === currentGroup.groupName,
|
||||
)
|
||||
let returnValue = { ...options }
|
||||
if (matchingGroup && isGroupWithOverridesOption(matchingGroup)) {
|
||||
let {
|
||||
newlinesInside,
|
||||
commentAbove,
|
||||
fallbackSort,
|
||||
group,
|
||||
...relevantGroupFields
|
||||
} = matchingGroup
|
||||
returnValue = {
|
||||
...returnValue,
|
||||
...relevantGroupFields,
|
||||
fallbackSort: {
|
||||
...returnValue.fallbackSort,
|
||||
...fallbackSort,
|
||||
},
|
||||
}
|
||||
}
|
||||
if (customGroup) {
|
||||
let {
|
||||
elementNamePattern,
|
||||
newlinesInside,
|
||||
fallbackSort,
|
||||
groupName,
|
||||
...relevantCustomGroupFields
|
||||
} = customGroup
|
||||
returnValue = {
|
||||
...returnValue,
|
||||
...relevantCustomGroupFields,
|
||||
fallbackSort: {
|
||||
...returnValue.fallbackSort,
|
||||
...fallbackSort,
|
||||
},
|
||||
}
|
||||
}
|
||||
return returnValue
|
||||
}
|
||||
export { computeOverriddenOptionsByGroupIndex }
|
||||
Generated
Vendored
+28
@@ -0,0 +1,28 @@
|
||||
import { AST_NODE_TYPES } from '@typescript-eslint/utils'
|
||||
import { TSESTree } from '@typescript-eslint/types'
|
||||
import { NodeOfType } from '../types/node-of-type.js'
|
||||
/**
|
||||
* Finds all parent nodes matching one of the specified AST node types.
|
||||
*
|
||||
* @param options - Options for the search.
|
||||
* @param options.allowedTypes - Array of AST node types to match.
|
||||
* @param options.consecutiveOnly - If true, stops searching after the first
|
||||
* non-matching parent node is found.
|
||||
* @param options.node - Starting node to search from.
|
||||
* @param options.maxParent - Optional maximum exclusive parent node to stop the
|
||||
* search at.
|
||||
* @returns List of matching parent nodes.
|
||||
*/
|
||||
export declare function computeParentNodesWithTypes<
|
||||
NodeType extends AST_NODE_TYPES,
|
||||
>({
|
||||
consecutiveOnly,
|
||||
allowedTypes,
|
||||
maxParent,
|
||||
node,
|
||||
}: {
|
||||
maxParent: TSESTree.Node | null
|
||||
consecutiveOnly: boolean
|
||||
allowedTypes: NodeType[]
|
||||
node: TSESTree.Node
|
||||
}): NodeOfType<NodeType>[]
|
||||
Generated
Vendored
+35
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Finds all parent nodes matching one of the specified AST node types.
|
||||
*
|
||||
* @param options - Options for the search.
|
||||
* @param options.allowedTypes - Array of AST node types to match.
|
||||
* @param options.consecutiveOnly - If true, stops searching after the first
|
||||
* non-matching parent node is found.
|
||||
* @param options.node - Starting node to search from.
|
||||
* @param options.maxParent - Optional maximum exclusive parent node to stop the
|
||||
* search at.
|
||||
* @returns List of matching parent nodes.
|
||||
*/
|
||||
function computeParentNodesWithTypes({
|
||||
consecutiveOnly,
|
||||
allowedTypes,
|
||||
maxParent,
|
||||
node,
|
||||
}) {
|
||||
let allowedTypesSet = new Set(allowedTypes)
|
||||
let returnValue = []
|
||||
let { parent } = node
|
||||
while (parent) {
|
||||
if (parent === maxParent) {
|
||||
break
|
||||
}
|
||||
if (allowedTypesSet.has(parent.type)) {
|
||||
returnValue.push(parent)
|
||||
} else if (consecutiveOnly) {
|
||||
break
|
||||
}
|
||||
;({ parent } = parent)
|
||||
}
|
||||
return returnValue
|
||||
}
|
||||
export { computeParentNodesWithTypes }
|
||||
Generated
Vendored
+18
@@ -0,0 +1,18 @@
|
||||
import { RegexOption } from '../../types/common-options.js'
|
||||
/**
|
||||
* Checks if all node names match the specified pattern.
|
||||
*
|
||||
* @param params - The parameters object.
|
||||
* @param params.allNamesMatchPattern - The pattern to match against all node
|
||||
* names.
|
||||
* @param params.nodeNames - Array of node names to test against patterns.
|
||||
* @returns True if all node names match the specified pattern, or if no pattern
|
||||
* is specified; otherwise, false.
|
||||
*/
|
||||
export declare function passesAllNamesMatchPatternFilter({
|
||||
allNamesMatchPattern,
|
||||
nodeNames,
|
||||
}: {
|
||||
allNamesMatchPattern?: RegexOption
|
||||
nodeNames: string[]
|
||||
}): boolean
|
||||
Generated
Vendored
+18
@@ -0,0 +1,18 @@
|
||||
import { matches } from '../matches.js'
|
||||
/**
|
||||
* Checks if all node names match the specified pattern.
|
||||
*
|
||||
* @param params - The parameters object.
|
||||
* @param params.allNamesMatchPattern - The pattern to match against all node
|
||||
* names.
|
||||
* @param params.nodeNames - Array of node names to test against patterns.
|
||||
* @returns True if all node names match the specified pattern, or if no pattern
|
||||
* is specified; otherwise, false.
|
||||
*/
|
||||
function passesAllNamesMatchPatternFilter({ allNamesMatchPattern, nodeNames }) {
|
||||
if (!allNamesMatchPattern) {
|
||||
return true
|
||||
}
|
||||
return nodeNames.every(nodeName => matches(nodeName, allNamesMatchPattern))
|
||||
}
|
||||
export { passesAllNamesMatchPatternFilter }
|
||||
Generated
Vendored
+17
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Checks if the given AST selector matches the expected AST selector.
|
||||
*
|
||||
* @param params - The parameters object.
|
||||
* @param params.matchesAstSelector - The AST selector to match against, or
|
||||
* undefined if no selector is specified.
|
||||
* @param params.matchedAstSelectors - The matched AST selectors for a node.
|
||||
* @returns True if the given AST selector matches the expected AST selector, or
|
||||
* if no selector is specified; otherwise, false.
|
||||
*/
|
||||
export declare function passesAstSelectorFilter({
|
||||
matchedAstSelectors,
|
||||
matchesAstSelector,
|
||||
}: {
|
||||
matchedAstSelectors: ReadonlySet<string>
|
||||
matchesAstSelector: undefined | string
|
||||
}): boolean
|
||||
Generated
Vendored
+17
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Checks if the given AST selector matches the expected AST selector.
|
||||
*
|
||||
* @param params - The parameters object.
|
||||
* @param params.matchesAstSelector - The AST selector to match against, or
|
||||
* undefined if no selector is specified.
|
||||
* @param params.matchedAstSelectors - The matched AST selectors for a node.
|
||||
* @returns True if the given AST selector matches the expected AST selector, or
|
||||
* if no selector is specified; otherwise, false.
|
||||
*/
|
||||
function passesAstSelectorFilter({ matchedAstSelectors, matchesAstSelector }) {
|
||||
if (!matchesAstSelector) {
|
||||
return true
|
||||
}
|
||||
return matchedAstSelectors.has(matchesAstSelector)
|
||||
}
|
||||
export { passesAstSelectorFilter }
|
||||
Generated
Vendored
+27
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Converts a boolean value to a sort direction multiplier.
|
||||
*
|
||||
* Used in sorting functions to convert boolean comparisons into numeric values
|
||||
* suitable for array sort callbacks. This allows for concise expression of sort
|
||||
* direction logic.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // In ascending sort
|
||||
* convertBooleanToSign(true) // Returns: 1
|
||||
* convertBooleanToSign(false) // Returns: -1
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Usage in sorting
|
||||
* const sortMultiplier = convertBooleanToSign(order === 'asc')
|
||||
* return sortMultiplier * (a - b)
|
||||
* ```
|
||||
*
|
||||
* @param value - Boolean value to convert to a sign.
|
||||
* @returns 1 if value is true, -1 if value is false.
|
||||
*/
|
||||
export declare function convertBooleanToSign(value: boolean): -1 | 1
|
||||
Generated
Vendored
+30
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Converts a boolean value to a sort direction multiplier.
|
||||
*
|
||||
* Used in sorting functions to convert boolean comparisons into numeric values
|
||||
* suitable for array sort callbacks. This allows for concise expression of sort
|
||||
* direction logic.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // In ascending sort
|
||||
* convertBooleanToSign(true) // Returns: 1
|
||||
* convertBooleanToSign(false) // Returns: -1
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Usage in sorting
|
||||
* const sortMultiplier = convertBooleanToSign(order === 'asc')
|
||||
* return sortMultiplier * (a - b)
|
||||
* ```
|
||||
*
|
||||
* @param value - Boolean value to convert to a sign.
|
||||
* @returns 1 if value is true, -1 if value is false.
|
||||
*/
|
||||
function convertBooleanToSign(value) {
|
||||
return value ? 1 : -1
|
||||
}
|
||||
export { convertBooleanToSign }
|
||||
Generated
Vendored
+48
@@ -0,0 +1,48 @@
|
||||
import { ESLintUtils } from '@typescript-eslint/utils'
|
||||
/**
|
||||
* Factory function for creating ESLint rules with consistent structure and
|
||||
* documentation.
|
||||
*
|
||||
* Wraps the ESLintUtils.RuleCreator to automatically generate documentation
|
||||
* URLs for each rule based on its name. All rules created with this function
|
||||
* will have their documentation hosted at perfectionist.dev.
|
||||
*
|
||||
* @see {@link https://typescript-eslint.io/packages/utils/} - TypeScript ESLint
|
||||
* Utils documentation
|
||||
* @see {@link https://perfectionist.dev/} - Perfectionist plugin documentation
|
||||
*/
|
||||
export declare let createEslintRule: <
|
||||
Options extends readonly unknown[],
|
||||
MessageIds extends string,
|
||||
>({
|
||||
meta,
|
||||
name,
|
||||
...rule
|
||||
}: Readonly<
|
||||
ESLintUtils.RuleWithMetaAndName<
|
||||
Options,
|
||||
MessageIds,
|
||||
{
|
||||
/**
|
||||
* Indicates whether the rule is part of the recommended configuration.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
recommended?: boolean
|
||||
}
|
||||
>
|
||||
>) => ESLintUtils.RuleModule<
|
||||
MessageIds,
|
||||
Options,
|
||||
{
|
||||
/**
|
||||
* Indicates whether the rule is part of the recommended configuration.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
recommended?: boolean
|
||||
},
|
||||
ESLintUtils.RuleListener
|
||||
> & {
|
||||
name: string
|
||||
}
|
||||
Generated
Vendored
+17
@@ -0,0 +1,17 @@
|
||||
import { ESLintUtils } from '@typescript-eslint/utils'
|
||||
/**
|
||||
* Factory function for creating ESLint rules with consistent structure and
|
||||
* documentation.
|
||||
*
|
||||
* Wraps the ESLintUtils.RuleCreator to automatically generate documentation
|
||||
* URLs for each rule based on its name. All rules created with this function
|
||||
* will have their documentation hosted at perfectionist.dev.
|
||||
*
|
||||
* @see {@link https://typescript-eslint.io/packages/utils/} - TypeScript ESLint
|
||||
* Utils documentation
|
||||
* @see {@link https://perfectionist.dev/} - Perfectionist plugin documentation
|
||||
*/
|
||||
var createEslintRule = ESLintUtils.RuleCreator(
|
||||
ruleName => `https://perfectionist.dev/rules/${ruleName}`,
|
||||
)
|
||||
export { createEslintRule }
|
||||
Generated
Vendored
+30
@@ -0,0 +1,30 @@
|
||||
import { TSESTree } from '@typescript-eslint/types'
|
||||
import { SortingNode } from '../types/sorting-node.js'
|
||||
/**
|
||||
* Creates a Map for efficient lookup of node positions in the sorted array.
|
||||
*
|
||||
* Builds an index map that associates each sorting node with its position in
|
||||
* the array. This is used to quickly determine the relative order of nodes
|
||||
* without repeated array searches, improving performance when generating error
|
||||
* messages for incorrectly sorted elements.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* const nodes = [
|
||||
* { name: 'foo', node: fooNode },
|
||||
* { name: 'bar', node: barNode },
|
||||
* { name: 'baz', node: bazNode },
|
||||
* ]
|
||||
* const indexMap = createNodeIndexMap(nodes)
|
||||
* indexMap.get(nodes[0]) // Returns: 0
|
||||
* indexMap.get(nodes[2]) // Returns: 2
|
||||
* ```
|
||||
*
|
||||
* @template Node - Type of the AST node.
|
||||
* @param nodes - Array of sorting nodes in their sorted order.
|
||||
* @returns Map where keys are sorting nodes and values are their indices.
|
||||
*/
|
||||
export declare function createNodeIndexMap<Node extends TSESTree.Node>(
|
||||
nodes: SortingNode<Node>[],
|
||||
): Map<SortingNode, number>
|
||||
Generated
Vendored
+33
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Creates a Map for efficient lookup of node positions in the sorted array.
|
||||
*
|
||||
* Builds an index map that associates each sorting node with its position in
|
||||
* the array. This is used to quickly determine the relative order of nodes
|
||||
* without repeated array searches, improving performance when generating error
|
||||
* messages for incorrectly sorted elements.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* const nodes = [
|
||||
* { name: 'foo', node: fooNode },
|
||||
* { name: 'bar', node: barNode },
|
||||
* { name: 'baz', node: bazNode },
|
||||
* ]
|
||||
* const indexMap = createNodeIndexMap(nodes)
|
||||
* indexMap.get(nodes[0]) // Returns: 0
|
||||
* indexMap.get(nodes[2]) // Returns: 2
|
||||
* ```
|
||||
*
|
||||
* @template Node - Type of the AST node.
|
||||
* @param nodes - Array of sorting nodes in their sorted order.
|
||||
* @returns Map where keys are sorting nodes and values are their indices.
|
||||
*/
|
||||
function createNodeIndexMap(nodes) {
|
||||
let nodeIndexMap = /* @__PURE__ */ new Map()
|
||||
for (let [index, node] of nodes.entries()) {
|
||||
nodeIndexMap.set(node, index)
|
||||
}
|
||||
return nodeIndexMap
|
||||
}
|
||||
export { createNodeIndexMap }
|
||||
Generated
Vendored
+129
@@ -0,0 +1,129 @@
|
||||
import { AnyOfCustomGroup } from '../types/common-groups-options.js'
|
||||
import { RegexOption } from '../types/common-options.js'
|
||||
/**
|
||||
* Parameters for testing if an element matches a custom group.
|
||||
*
|
||||
* Contains all the properties of an element that can be used for matching
|
||||
* against custom group criteria.
|
||||
*/
|
||||
interface DoesCustomGroupMatchParameters {
|
||||
/**
|
||||
* Optional value of the element. Used for matching against
|
||||
* elementValuePattern in custom groups.
|
||||
*/
|
||||
elementValue?: string | null
|
||||
/**
|
||||
* Optional list of decorator names applied to the element. Used for
|
||||
* matching against decoratorNamePattern in custom groups.
|
||||
*/
|
||||
decorators?: string[]
|
||||
/**
|
||||
* List of modifiers applied to the element (e.g., 'static', 'private',
|
||||
* 'async'). Must include all modifiers specified in the custom group.
|
||||
*/
|
||||
modifiers: string[]
|
||||
/**
|
||||
* List of selectors that describe the element type. Used for matching
|
||||
* against the selector field in custom groups.
|
||||
*/
|
||||
selectors: string[]
|
||||
/**
|
||||
* Name of the element. Used for matching against elementNamePattern in
|
||||
* custom groups.
|
||||
*/
|
||||
elementName: string
|
||||
}
|
||||
/**
|
||||
* Base structure for a single custom group configuration.
|
||||
*
|
||||
* Defines matching criteria that an element must satisfy to belong to this
|
||||
* custom group. All specified criteria must match for the element to be
|
||||
* considered part of the group.
|
||||
*/
|
||||
interface BaseCustomGroupMatchOptions {
|
||||
/**
|
||||
* Pattern to match against decorator names. Element must have at least one
|
||||
* decorator matching this pattern.
|
||||
*/
|
||||
decoratorNamePattern?: RegexOption
|
||||
/**
|
||||
* Pattern to match against the element's value. Used for matching literal
|
||||
* values, initializers, or expressions.
|
||||
*/
|
||||
elementValuePattern?: RegexOption
|
||||
/**
|
||||
* Regular expression pattern to match the element's name. Elements matching
|
||||
* this pattern will be included in this custom group.
|
||||
*/
|
||||
elementNamePattern?: RegexOption
|
||||
/**
|
||||
* List of required modifiers. Element must have ALL specified modifiers to
|
||||
* match.
|
||||
*/
|
||||
modifiers?: string[]
|
||||
/**
|
||||
* Required selector type. Element must have this exact selector to match.
|
||||
*/
|
||||
selector?: string
|
||||
}
|
||||
/**
|
||||
* Checks whether an element matches the criteria of a custom group.
|
||||
*
|
||||
* Supports both single custom groups and "anyOf" groups (where matching any
|
||||
* subgroup is sufficient). For single groups, all specified criteria must
|
||||
* match. For "anyOf" groups, at least one subgroup must match.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Single custom group
|
||||
* doesCustomGroupMatch({
|
||||
* customGroup: {
|
||||
* selector: 'property',
|
||||
* modifiers: ['static'],
|
||||
* elementNamePattern: 'on*',
|
||||
* },
|
||||
* elementName: 'onClick',
|
||||
* selectors: ['property'],
|
||||
* modifiers: ['static', 'readonly'],
|
||||
* elementValue: null,
|
||||
* decorators: [],
|
||||
* })
|
||||
* // Returns: true
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // AnyOf custom group
|
||||
* doesCustomGroupMatch({
|
||||
* customGroup: {
|
||||
* anyOf: [
|
||||
* { selector: 'method' },
|
||||
* { selector: 'property', modifiers: ['static'] },
|
||||
* ],
|
||||
* },
|
||||
* elementName: 'foo',
|
||||
* selectors: ['method'],
|
||||
* modifiers: [],
|
||||
* elementValue: null,
|
||||
* })
|
||||
* // Returns: true (matches first subgroup)
|
||||
* ```
|
||||
*
|
||||
* @template CustomGroupMatchOptions - Type of custom group match options.
|
||||
* @param props - Combined parameters including the custom group and element
|
||||
* properties.
|
||||
* @returns True if the element matches the custom group criteria, false
|
||||
* otherwise.
|
||||
*/
|
||||
export declare function doesCustomGroupMatch<
|
||||
CustomGroupMatchOptions extends BaseCustomGroupMatchOptions,
|
||||
>(
|
||||
props: {
|
||||
customGroup:
|
||||
| AnyOfCustomGroup<CustomGroupMatchOptions>
|
||||
| CustomGroupMatchOptions
|
||||
} & DoesCustomGroupMatchParameters,
|
||||
): boolean
|
||||
export {}
|
||||
Generated
Vendored
+129
@@ -0,0 +1,129 @@
|
||||
import { matches } from './matches.js'
|
||||
/**
|
||||
* Checks whether an element matches the criteria of a custom group.
|
||||
*
|
||||
* Supports both single custom groups and "anyOf" groups (where matching any
|
||||
* subgroup is sufficient). For single groups, all specified criteria must
|
||||
* match. For "anyOf" groups, at least one subgroup must match.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Single custom group
|
||||
* doesCustomGroupMatch({
|
||||
* customGroup: {
|
||||
* selector: 'property',
|
||||
* modifiers: ['static'],
|
||||
* elementNamePattern: 'on*',
|
||||
* },
|
||||
* elementName: 'onClick',
|
||||
* selectors: ['property'],
|
||||
* modifiers: ['static', 'readonly'],
|
||||
* elementValue: null,
|
||||
* decorators: [],
|
||||
* })
|
||||
* // Returns: true
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // AnyOf custom group
|
||||
* doesCustomGroupMatch({
|
||||
* customGroup: {
|
||||
* anyOf: [
|
||||
* { selector: 'method' },
|
||||
* { selector: 'property', modifiers: ['static'] },
|
||||
* ],
|
||||
* },
|
||||
* elementName: 'foo',
|
||||
* selectors: ['method'],
|
||||
* modifiers: [],
|
||||
* elementValue: null,
|
||||
* })
|
||||
* // Returns: true (matches first subgroup)
|
||||
* ```
|
||||
*
|
||||
* @template CustomGroupMatchOptions - Type of custom group match options.
|
||||
* @param props - Combined parameters including the custom group and element
|
||||
* properties.
|
||||
* @returns True if the element matches the custom group criteria, false
|
||||
* otherwise.
|
||||
*/
|
||||
function doesCustomGroupMatch(props) {
|
||||
if ('anyOf' in props.customGroup) {
|
||||
return props.customGroup.anyOf.some(subgroup =>
|
||||
doesSingleCustomGroupMatch({
|
||||
...props,
|
||||
customGroup: subgroup,
|
||||
}),
|
||||
)
|
||||
}
|
||||
return doesSingleCustomGroupMatch({
|
||||
...props,
|
||||
customGroup: props.customGroup,
|
||||
})
|
||||
}
|
||||
/**
|
||||
* Checks whether an element matches a single custom group's criteria.
|
||||
*
|
||||
* Tests each criterion in sequence, returning false as soon as any criterion
|
||||
* fails. All specified criteria must match for the function to return true. The
|
||||
* checks are performed in the following order:
|
||||
*
|
||||
* 1. Selector match (exact)
|
||||
* 2. Modifiers match (all required modifiers must be present)
|
||||
* 3. Element name pattern match
|
||||
* 4. Element value pattern match
|
||||
* 5. Decorator name pattern match (at least one decorator must match).
|
||||
*
|
||||
* @param params - Parameters for matching.
|
||||
* @param params.customGroup - Custom group configuration with matching
|
||||
* criteria.
|
||||
* @param params.elementName - Name of the element to test.
|
||||
* @param params.elementValue - Optional value of the element.
|
||||
* @param params.selectors - Element's selectors.
|
||||
* @param params.modifiers - Element's modifiers.
|
||||
* @param params.decorators - Element's decorators.
|
||||
* @returns True if all specified criteria match, false otherwise.
|
||||
*/
|
||||
function doesSingleCustomGroupMatch({
|
||||
elementValue,
|
||||
customGroup,
|
||||
elementName,
|
||||
decorators,
|
||||
selectors,
|
||||
modifiers,
|
||||
}) {
|
||||
if (customGroup.selector && !selectors?.includes(customGroup.selector)) {
|
||||
return false
|
||||
}
|
||||
if (customGroup.modifiers) {
|
||||
for (let modifier of customGroup.modifiers) {
|
||||
if (!modifiers?.includes(modifier)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
if ('elementNamePattern' in customGroup && customGroup.elementNamePattern) {
|
||||
if (!matches(elementName, customGroup.elementNamePattern)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if ('elementValuePattern' in customGroup && customGroup.elementValuePattern) {
|
||||
if (!matches(elementValue ?? '', customGroup.elementValuePattern)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if (
|
||||
'decoratorNamePattern' in customGroup &&
|
||||
customGroup.decoratorNamePattern
|
||||
) {
|
||||
let decoratorPattern = customGroup.decoratorNamePattern
|
||||
if (!decorators?.some(decorator => matches(decorator, decoratorPattern))) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
export { doesCustomGroupMatch }
|
||||
Generated
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
import { SortingNodeWithDependencies } from './sort-nodes-by-dependencies.js'
|
||||
/**
|
||||
* Checks whether the given sorting node has at least one of the given
|
||||
* dependency names.
|
||||
*
|
||||
* @param sortingNode - The sorting node to check.
|
||||
* @param dependencyNames - The dependency names to look for.
|
||||
* @returns True if the sorting node has at least one of the dependency names,
|
||||
* false otherwise.
|
||||
*/
|
||||
export declare function doesSortingNodeHaveOneOfDependencyNames(
|
||||
sortingNode: Pick<SortingNodeWithDependencies, 'dependencyNames'>,
|
||||
dependencyNames: string[],
|
||||
): boolean
|
||||
Generated
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Checks whether the given sorting node has at least one of the given
|
||||
* dependency names.
|
||||
*
|
||||
* @param sortingNode - The sorting node to check.
|
||||
* @param dependencyNames - The dependency names to look for.
|
||||
* @returns True if the sorting node has at least one of the dependency names,
|
||||
* false otherwise.
|
||||
*/
|
||||
function doesSortingNodeHaveOneOfDependencyNames(sortingNode, dependencyNames) {
|
||||
let sortingNodeDependencyNames = new Set(sortingNode.dependencyNames)
|
||||
return dependencyNames.some(dependencyName =>
|
||||
sortingNodeDependencyNames.has(dependencyName),
|
||||
)
|
||||
}
|
||||
export { doesSortingNodeHaveOneOfDependencyNames }
|
||||
Generated
Vendored
+25
@@ -0,0 +1,25 @@
|
||||
interface GeneratePredefinedGroupsParameters {
|
||||
cache: Map<string, string[]>
|
||||
modifiers: string[]
|
||||
selectors: string[]
|
||||
}
|
||||
/**
|
||||
* Generates an ordered list of group names associated with the provided
|
||||
* modifiers and selectors. The groups are generated by combining all possible
|
||||
* combinations of modifiers with each selector at the end. Selectors are
|
||||
* prioritized over the quantity of modifiers. For example, `protected abstract
|
||||
* override get fields();` should prioritize the `'get-method'` group over the
|
||||
* `'protected-abstract-override-method'` group.
|
||||
*
|
||||
* @param props - The properties including selectors, modifiers, and cache.
|
||||
* @param props.selectors - The list of selectors.
|
||||
* @param props.modifiers - The list of modifiers.
|
||||
* @param props.cache - Cache to store computed groups.
|
||||
* @returns An array of generated group names.
|
||||
*/
|
||||
export declare function generatePredefinedGroups({
|
||||
selectors,
|
||||
modifiers,
|
||||
cache,
|
||||
}: GeneratePredefinedGroupsParameters): string[]
|
||||
export {}
|
||||
Generated
Vendored
+70
@@ -0,0 +1,70 @@
|
||||
import { getArrayCombinations } from './get-array-combinations.js'
|
||||
/**
|
||||
* Generates an ordered list of group names associated with the provided
|
||||
* modifiers and selectors. The groups are generated by combining all possible
|
||||
* combinations of modifiers with each selector at the end. Selectors are
|
||||
* prioritized over the quantity of modifiers. For example, `protected abstract
|
||||
* override get fields();` should prioritize the `'get-method'` group over the
|
||||
* `'protected-abstract-override-method'` group.
|
||||
*
|
||||
* @param props - The properties including selectors, modifiers, and cache.
|
||||
* @param props.selectors - The list of selectors.
|
||||
* @param props.modifiers - The list of modifiers.
|
||||
* @param props.cache - Cache to store computed groups.
|
||||
* @returns An array of generated group names.
|
||||
*/
|
||||
function generatePredefinedGroups({ selectors, modifiers, cache }) {
|
||||
let modifiersAndSelectorsKey = `${modifiers.join('&')}/${selectors.join('&')}`
|
||||
let cachedValue = cache.get(modifiersAndSelectorsKey)
|
||||
if (cachedValue) {
|
||||
return cachedValue
|
||||
}
|
||||
let allModifiersCombinations = []
|
||||
for (let i = modifiers.length; i > 0; i--) {
|
||||
allModifiersCombinations.push(...getArrayCombinations(modifiers, i))
|
||||
}
|
||||
let allModifiersCombinationPermutations = allModifiersCombinations.flatMap(
|
||||
result => getPermutations(result),
|
||||
)
|
||||
let returnValue = []
|
||||
for (let selector of selectors) {
|
||||
returnValue.push(
|
||||
...allModifiersCombinationPermutations.map(
|
||||
modifiersCombinationPermutation =>
|
||||
[...modifiersCombinationPermutation, selector].join('-'),
|
||||
),
|
||||
selector,
|
||||
)
|
||||
}
|
||||
cache.set(modifiersAndSelectorsKey, returnValue)
|
||||
return returnValue
|
||||
}
|
||||
/**
|
||||
* Generates all permutations of an array. This allows variations like
|
||||
* `'abstract-override-protected-get-method'`,
|
||||
* `'override-protected-abstract-get-method'`,
|
||||
* `'protected-abstract-override-get-method'`, etc., to be entered by the user
|
||||
* and always match the same group. Note that this can theoretically cause
|
||||
* performance issues if too many modifiers are entered at once (e.g., 8
|
||||
* modifiers result in 40,320 permutations, 9 in 362,880).
|
||||
*
|
||||
* @param elements - The array of elements to permute.
|
||||
* @returns An array containing all permutations of the input elements.
|
||||
*/
|
||||
function getPermutations(elements) {
|
||||
let result = []
|
||||
function backtrack(first) {
|
||||
if (first === elements.length) {
|
||||
result.push([...elements])
|
||||
return
|
||||
}
|
||||
for (let i = first; i < elements.length; i++) {
|
||||
;[elements[first], elements[i]] = [elements[i], elements[first]]
|
||||
backtrack(first + 1)
|
||||
;[elements[first], elements[i]] = [elements[i], elements[first]]
|
||||
}
|
||||
}
|
||||
backtrack(0)
|
||||
return result
|
||||
}
|
||||
export { generatePredefinedGroups }
|
||||
Generated
Vendored
+11
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Generates all possible combinations of a specific size from an array.
|
||||
*
|
||||
* @param array - The array of strings to generate combinations from.
|
||||
* @param number - The number of elements in each combination.
|
||||
* @returns An array containing all possible combinations.
|
||||
*/
|
||||
export declare function getArrayCombinations(
|
||||
array: string[],
|
||||
number: number,
|
||||
): string[][]
|
||||
Generated
Vendored
+24
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Generates all possible combinations of a specific size from an array.
|
||||
*
|
||||
* @param array - The array of strings to generate combinations from.
|
||||
* @param number - The number of elements in each combination.
|
||||
* @returns An array containing all possible combinations.
|
||||
*/
|
||||
function getArrayCombinations(array, number) {
|
||||
let result = []
|
||||
function backtrack(start, comb) {
|
||||
if (comb.length === number) {
|
||||
result.push([...comb])
|
||||
return
|
||||
}
|
||||
for (let i = start; i < array.length; i++) {
|
||||
comb.push(array[i])
|
||||
backtrack(i + 1, comb)
|
||||
comb.pop()
|
||||
}
|
||||
}
|
||||
backtrack(0, [])
|
||||
return result
|
||||
}
|
||||
export { getArrayCombinations }
|
||||
Generated
Vendored
+88
@@ -0,0 +1,88 @@
|
||||
import { TSESLint } from '@typescript-eslint/utils'
|
||||
import { CommonGroupsOptions } from '../types/common-groups-options.js'
|
||||
import { SortingNode } from '../types/sorting-node.js'
|
||||
/**
|
||||
* Parameters for checking if a required comment above a node exists.
|
||||
*
|
||||
* @template T - Type of the sorting node.
|
||||
*/
|
||||
interface GetCommentAboveMissingParameters<T extends SortingNode> {
|
||||
/**
|
||||
* Configuration options for grouping.
|
||||
*/
|
||||
options: Pick<
|
||||
CommonGroupsOptions<string, unknown, unknown>,
|
||||
'customGroups' | 'groups'
|
||||
>
|
||||
/**
|
||||
* ESLint source code object for accessing comments.
|
||||
*/
|
||||
sourceCode: TSESLint.SourceCode
|
||||
/**
|
||||
* Index of the group on the left side (previous element). Null if there is
|
||||
* no previous element.
|
||||
*/
|
||||
leftGroupIndex: number | null
|
||||
/**
|
||||
* Index of the group on the right side (current element).
|
||||
*/
|
||||
rightGroupIndex: number
|
||||
/**
|
||||
* The sorting node to check for required comments above.
|
||||
*/
|
||||
sortingNode: T
|
||||
}
|
||||
/**
|
||||
* Determines if a comment should exist above a node when transitioning between
|
||||
* groups.
|
||||
*
|
||||
* Checks if the group configuration requires a comment above nodes when they
|
||||
* are the first element of their group following a different group. This is
|
||||
* used to enforce comment separators between different sections of sorted
|
||||
* code.
|
||||
*
|
||||
* The function returns null if:
|
||||
*
|
||||
* - The left and right elements are in the same or reversed group order
|
||||
* - The group configuration doesn't require a comment above.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* const result = getCommentAboveThatShouldExist({
|
||||
* options: {
|
||||
* groups: [
|
||||
* 'external',
|
||||
* { commentAbove: 'Internal imports' },
|
||||
* 'internal',
|
||||
* ],
|
||||
* },
|
||||
* leftGroupIndex: 0, // 'external' group
|
||||
* rightGroupIndex: 2, // 'internal' group
|
||||
* sortingNode: internalImportNode,
|
||||
* sourceCode,
|
||||
* })
|
||||
* // Returns: { comment: 'Internal imports', exists: false }
|
||||
* ```
|
||||
*
|
||||
* @template T - Type of the sorting node.
|
||||
* @param params - Parameters for checking comment requirements.
|
||||
* @param params.options - Configuration with groups that may require comments.
|
||||
* @param params.leftGroupIndex - Index of the previous element's group.
|
||||
* @param params.rightGroupIndex - Index of the current element's group.
|
||||
* @param params.sortingNode - Node to check for required comments.
|
||||
* @param params.sourceCode - ESLint source code for accessing comments.
|
||||
* @returns Object with required comment text and existence status, or null if
|
||||
* no comment required.
|
||||
*/
|
||||
export declare function getCommentAboveThatShouldExist<T extends SortingNode>({
|
||||
rightGroupIndex,
|
||||
leftGroupIndex,
|
||||
sortingNode,
|
||||
sourceCode,
|
||||
options,
|
||||
}: GetCommentAboveMissingParameters<T>): {
|
||||
comment: string
|
||||
exists: boolean
|
||||
} | null
|
||||
export {}
|
||||
Generated
Vendored
+75
@@ -0,0 +1,75 @@
|
||||
import { isGroupWithOverridesOption } from './is-group-with-overrides-option.js'
|
||||
import { getCommentsBefore } from './get-comments-before.js'
|
||||
/**
|
||||
* Determines if a comment should exist above a node when transitioning between
|
||||
* groups.
|
||||
*
|
||||
* Checks if the group configuration requires a comment above nodes when they
|
||||
* are the first element of their group following a different group. This is
|
||||
* used to enforce comment separators between different sections of sorted
|
||||
* code.
|
||||
*
|
||||
* The function returns null if:
|
||||
*
|
||||
* - The left and right elements are in the same or reversed group order
|
||||
* - The group configuration doesn't require a comment above.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* const result = getCommentAboveThatShouldExist({
|
||||
* options: {
|
||||
* groups: [
|
||||
* 'external',
|
||||
* { commentAbove: 'Internal imports' },
|
||||
* 'internal',
|
||||
* ],
|
||||
* },
|
||||
* leftGroupIndex: 0, // 'external' group
|
||||
* rightGroupIndex: 2, // 'internal' group
|
||||
* sortingNode: internalImportNode,
|
||||
* sourceCode,
|
||||
* })
|
||||
* // Returns: { comment: 'Internal imports', exists: false }
|
||||
* ```
|
||||
*
|
||||
* @template T - Type of the sorting node.
|
||||
* @param params - Parameters for checking comment requirements.
|
||||
* @param params.options - Configuration with groups that may require comments.
|
||||
* @param params.leftGroupIndex - Index of the previous element's group.
|
||||
* @param params.rightGroupIndex - Index of the current element's group.
|
||||
* @param params.sortingNode - Node to check for required comments.
|
||||
* @param params.sourceCode - ESLint source code for accessing comments.
|
||||
* @returns Object with required comment text and existence status, or null if
|
||||
* no comment required.
|
||||
*/
|
||||
function getCommentAboveThatShouldExist({
|
||||
rightGroupIndex,
|
||||
leftGroupIndex,
|
||||
sortingNode,
|
||||
sourceCode,
|
||||
options,
|
||||
}) {
|
||||
if (leftGroupIndex !== null && leftGroupIndex >= rightGroupIndex) {
|
||||
return null
|
||||
}
|
||||
let rightGroup = options.groups[rightGroupIndex]
|
||||
if (!rightGroup || !isGroupWithOverridesOption(rightGroup)) {
|
||||
return null
|
||||
}
|
||||
let rightGroupCommentAbove = rightGroup.commentAbove
|
||||
if (!rightGroupCommentAbove) {
|
||||
return null
|
||||
}
|
||||
return {
|
||||
comment: rightGroupCommentAbove,
|
||||
exists: !!getCommentsBefore({
|
||||
node: sortingNode.node,
|
||||
sourceCode,
|
||||
}).find(comment => commentMatches(comment.value, rightGroupCommentAbove)),
|
||||
}
|
||||
}
|
||||
function commentMatches(comment, expected) {
|
||||
return comment.toLowerCase().includes(expected.toLowerCase().trim())
|
||||
}
|
||||
export { getCommentAboveThatShouldExist }
|
||||
Generated
Vendored
+24
@@ -0,0 +1,24 @@
|
||||
import { TSESLint } from '@typescript-eslint/utils'
|
||||
import { TSESTree } from '@typescript-eslint/types'
|
||||
interface GetCommentsBeforeParameters {
|
||||
tokenValueToIgnoreBefore?: string
|
||||
sourceCode: TSESLint.SourceCode
|
||||
node: TSESTree.Node
|
||||
}
|
||||
/**
|
||||
* Returns a list of comments before a given node, excluding ones that are right
|
||||
* after code. Includes comment blocks, ignore shebang comments.
|
||||
*
|
||||
* @param params - Parameters object.
|
||||
* @param params.node - The node to get comments before.
|
||||
* @param params.sourceCode - The source code object.
|
||||
* @param [params.tokenValueToIgnoreBefore] - Allows the following token to
|
||||
* directly precede the node.
|
||||
* @returns An array of comments before the given node.
|
||||
*/
|
||||
export declare function getCommentsBefore({
|
||||
tokenValueToIgnoreBefore,
|
||||
sourceCode,
|
||||
node,
|
||||
}: GetCommentsBeforeParameters): TSESTree.Comment[]
|
||||
export {}
|
||||
Generated
Vendored
+37
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Returns a list of comments before a given node, excluding ones that are right
|
||||
* after code. Includes comment blocks, ignore shebang comments.
|
||||
*
|
||||
* @param params - Parameters object.
|
||||
* @param params.node - The node to get comments before.
|
||||
* @param params.sourceCode - The source code object.
|
||||
* @param [params.tokenValueToIgnoreBefore] - Allows the following token to
|
||||
* directly precede the node.
|
||||
* @returns An array of comments before the given node.
|
||||
*/
|
||||
function getCommentsBefore({ tokenValueToIgnoreBefore, sourceCode, node }) {
|
||||
let commentsBefore = getRelevantCommentsBeforeNodeOrToken(sourceCode, node)
|
||||
let tokenBeforeNode = sourceCode.getTokenBefore(node)
|
||||
if (
|
||||
commentsBefore.length > 0 ||
|
||||
!tokenValueToIgnoreBefore ||
|
||||
tokenBeforeNode?.value !== tokenValueToIgnoreBefore
|
||||
) {
|
||||
return commentsBefore
|
||||
}
|
||||
return getRelevantCommentsBeforeNodeOrToken(sourceCode, tokenBeforeNode)
|
||||
}
|
||||
function getRelevantCommentsBeforeNodeOrToken(source, node) {
|
||||
return source
|
||||
.getCommentsBefore(node)
|
||||
.filter(comment => !isShebangComment(comment))
|
||||
.filter(comment => {
|
||||
return (
|
||||
source.getTokenBefore(comment)?.loc.end.line !== comment.loc.end.line
|
||||
)
|
||||
})
|
||||
}
|
||||
function isShebangComment(comment) {
|
||||
return comment.type === 'Shebang' || comment.type === 'Hashbang'
|
||||
}
|
||||
export { getCommentsBefore }
|
||||
Generated
Vendored
+45
@@ -0,0 +1,45 @@
|
||||
import { TSESTree } from '@typescript-eslint/types'
|
||||
import { TSESLint } from '@typescript-eslint/utils'
|
||||
/**
|
||||
* Extracts the name of a decorator from its AST node.
|
||||
*
|
||||
* Processes the decorator text to extract just the name portion, removing the
|
||||
* '@' prefix if present and any parameters or arguments that follow the name.
|
||||
* This is useful for sorting and matching decorators by their base name.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Simple decorator
|
||||
* getDecoratorName({ sourceCode, decorator: @Component });
|
||||
* // Returns: 'Component'
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Decorator with parameters
|
||||
* getDecoratorName({ sourceCode, decorator: @Injectable({ providedIn: 'root' }) });
|
||||
* // Returns: 'Injectable'
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Namespaced decorator
|
||||
* getDecoratorName({ sourceCode, decorator: @angular.Component() });
|
||||
* // Returns: 'angular.Component'
|
||||
* ```
|
||||
*
|
||||
* @param params - Parameters object.
|
||||
* @param params.sourceCode - ESLint source code object for text extraction.
|
||||
* @param params.decorator - Decorator AST node to extract name from.
|
||||
* @returns The decorator name without '@' prefix and parameters.
|
||||
*/
|
||||
export declare function getDecoratorName({
|
||||
sourceCode,
|
||||
decorator,
|
||||
}: {
|
||||
sourceCode: TSESLint.SourceCode
|
||||
decorator: TSESTree.Decorator
|
||||
}): string
|
||||
Generated
Vendored
+44
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Extracts the name of a decorator from its AST node.
|
||||
*
|
||||
* Processes the decorator text to extract just the name portion, removing the
|
||||
* '@' prefix if present and any parameters or arguments that follow the name.
|
||||
* This is useful for sorting and matching decorators by their base name.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Simple decorator
|
||||
* getDecoratorName({ sourceCode, decorator: @Component });
|
||||
* // Returns: 'Component'
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Decorator with parameters
|
||||
* getDecoratorName({ sourceCode, decorator: @Injectable({ providedIn: 'root' }) });
|
||||
* // Returns: 'Injectable'
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Namespaced decorator
|
||||
* getDecoratorName({ sourceCode, decorator: @angular.Component() });
|
||||
* // Returns: 'angular.Component'
|
||||
* ```
|
||||
*
|
||||
* @param params - Parameters object.
|
||||
* @param params.sourceCode - ESLint source code object for text extraction.
|
||||
* @param params.decorator - Decorator AST node to extract name from.
|
||||
* @returns The decorator name without '@' prefix and parameters.
|
||||
*/
|
||||
function getDecoratorName({ sourceCode, decorator }) {
|
||||
let fullName = sourceCode.getText(decorator)
|
||||
if (fullName.startsWith('@')) {
|
||||
fullName = fullName.slice(1)
|
||||
}
|
||||
return fullName.split('(')[0]
|
||||
}
|
||||
export { getDecoratorName }
|
||||
Generated
Vendored
+28
@@ -0,0 +1,28 @@
|
||||
import { TSESTree } from '@typescript-eslint/types'
|
||||
/**
|
||||
* Retrieves enum members from a TypeScript enum declaration node.
|
||||
*
|
||||
* Handles AST shape changes in TS-ESTree `@typescript-eslint/types`:
|
||||
*
|
||||
* - Newer parser versions wrap enum members in `body.members` and deprecate
|
||||
* `members` on the enum node.
|
||||
* - Older parser versions expose members directly on the enum node as `members`.
|
||||
* The fallback keeps backward compatibility with older parser releases.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* enum Color {
|
||||
* Red = 'RED',
|
||||
* Green = 'GREEN',
|
||||
* Blue = 'BLUE',
|
||||
* }
|
||||
* // Returns array of three TSEnumMember nodes
|
||||
* ```
|
||||
*
|
||||
* @param value - TypeScript enum declaration AST node.
|
||||
* @returns Array of enum member nodes.
|
||||
*/
|
||||
export declare function getEnumMembers(
|
||||
value: TSESTree.TSEnumDeclaration,
|
||||
): TSESTree.TSEnumMember[]
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Retrieves enum members from a TypeScript enum declaration node.
|
||||
*
|
||||
* Handles AST shape changes in TS-ESTree `@typescript-eslint/types`:
|
||||
*
|
||||
* - Newer parser versions wrap enum members in `body.members` and deprecate
|
||||
* `members` on the enum node.
|
||||
* - Older parser versions expose members directly on the enum node as `members`.
|
||||
* The fallback keeps backward compatibility with older parser releases.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* enum Color {
|
||||
* Red = 'RED',
|
||||
* Green = 'GREEN',
|
||||
* Blue = 'BLUE',
|
||||
* }
|
||||
* // Returns array of three TSEnumMember nodes
|
||||
* ```
|
||||
*
|
||||
* @param value - TypeScript enum declaration AST node.
|
||||
* @returns Array of enum member nodes.
|
||||
*/
|
||||
function getEnumMembers(value) {
|
||||
return value.body?.members ?? value.members
|
||||
}
|
||||
export { getEnumMembers }
|
||||
Generated
Vendored
+47
@@ -0,0 +1,47 @@
|
||||
import { TSESLint } from '@typescript-eslint/utils'
|
||||
/**
|
||||
* Determines which lines have the specified ESLint rule disabled via comments.
|
||||
*
|
||||
* Parses ESLint disable comments in the source code to identify lines where a
|
||||
* specific rule should not be enforced. Handles all ESLint disable directives:
|
||||
*
|
||||
* - `eslint-disable-next-line` - Disables the rule for the next line
|
||||
* - `eslint-disable-line` - Disables the rule for the current line
|
||||
* - `eslint-disable` - Disables the rule from this point forward
|
||||
* - `eslint-enable` - Re-enables the rule after a previous disable.
|
||||
*
|
||||
* The function correctly handles:
|
||||
*
|
||||
* - Rule-specific disables (e.g., `eslint-disable-next-line rule-name`)
|
||||
* - Global disables (e.g., `eslint-disable-next-line` without specific rules)
|
||||
* - Nested disable/enable pairs.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Source code with disable comments:
|
||||
* // eslint-disable-next-line perfectionist/sort-imports
|
||||
* import { z } from 'zod'
|
||||
* import { a } from 'a'
|
||||
*
|
||||
* // eslint-disable perfectionist/sort-imports
|
||||
* import { y } from 'y'
|
||||
* import { b } from 'b'
|
||||
* // eslint-enable perfectionist/sort-imports
|
||||
*
|
||||
* getEslintDisabledLines({
|
||||
* sourceCode,
|
||||
* ruleName: 'perfectionist/sort-imports',
|
||||
* })
|
||||
* // Returns: [2, 5, 6] (lines where the rule is disabled)
|
||||
* ```
|
||||
*
|
||||
* @param props - Configuration object.
|
||||
* @param props.sourceCode - ESLint source code object containing comments.
|
||||
* @param props.ruleName - Name of the rule to check for disable directives.
|
||||
* @returns Array of line numbers (1-indexed) where the rule is disabled.
|
||||
*/
|
||||
export declare function getEslintDisabledLines(props: {
|
||||
sourceCode: TSESLint.SourceCode
|
||||
ruleName: string
|
||||
}): number[]
|
||||
Generated
Vendored
+115
@@ -0,0 +1,115 @@
|
||||
import { UnreachableCaseError } from './unreachable-case-error.js'
|
||||
import { getEslintDisabledRules } from './get-eslint-disabled-rules.js'
|
||||
/**
|
||||
* Determines which lines have the specified ESLint rule disabled via comments.
|
||||
*
|
||||
* Parses ESLint disable comments in the source code to identify lines where a
|
||||
* specific rule should not be enforced. Handles all ESLint disable directives:
|
||||
*
|
||||
* - `eslint-disable-next-line` - Disables the rule for the next line
|
||||
* - `eslint-disable-line` - Disables the rule for the current line
|
||||
* - `eslint-disable` - Disables the rule from this point forward
|
||||
* - `eslint-enable` - Re-enables the rule after a previous disable.
|
||||
*
|
||||
* The function correctly handles:
|
||||
*
|
||||
* - Rule-specific disables (e.g., `eslint-disable-next-line rule-name`)
|
||||
* - Global disables (e.g., `eslint-disable-next-line` without specific rules)
|
||||
* - Nested disable/enable pairs.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Source code with disable comments:
|
||||
* // eslint-disable-next-line perfectionist/sort-imports
|
||||
* import { z } from 'zod'
|
||||
* import { a } from 'a'
|
||||
*
|
||||
* // eslint-disable perfectionist/sort-imports
|
||||
* import { y } from 'y'
|
||||
* import { b } from 'b'
|
||||
* // eslint-enable perfectionist/sort-imports
|
||||
*
|
||||
* getEslintDisabledLines({
|
||||
* sourceCode,
|
||||
* ruleName: 'perfectionist/sort-imports',
|
||||
* })
|
||||
* // Returns: [2, 5, 6] (lines where the rule is disabled)
|
||||
* ```
|
||||
*
|
||||
* @param props - Configuration object.
|
||||
* @param props.sourceCode - ESLint source code object containing comments.
|
||||
* @param props.ruleName - Name of the rule to check for disable directives.
|
||||
* @returns Array of line numbers (1-indexed) where the rule is disabled.
|
||||
*/
|
||||
function getEslintDisabledLines(props) {
|
||||
let { sourceCode, ruleName } = props
|
||||
let returnValue = []
|
||||
let lineRulePermanentlyDisabled = null
|
||||
for (let comment of sourceCode.getAllComments()) {
|
||||
let eslintDisabledRules = getEslintDisabledRules(comment.value)
|
||||
if (!eslintDisabledRules) {
|
||||
continue
|
||||
}
|
||||
/* v8 ignore next 3 -- @preserve Hard to test this false branch. */
|
||||
if (
|
||||
!(
|
||||
eslintDisabledRules.rules === 'all' ||
|
||||
eslintDisabledRules.rules.includes(ruleName)
|
||||
)
|
||||
) {
|
||||
continue
|
||||
}
|
||||
switch (eslintDisabledRules.eslintDisableDirective) {
|
||||
case 'eslint-disable-next-line':
|
||||
returnValue.push(comment.loc.end.line + 1)
|
||||
continue
|
||||
case 'eslint-disable-line':
|
||||
returnValue.push(comment.loc.start.line)
|
||||
continue
|
||||
case 'eslint-disable':
|
||||
lineRulePermanentlyDisabled ??= comment.loc.start.line
|
||||
break
|
||||
case 'eslint-enable':
|
||||
/* v8 ignore next -- @preserve Hard to cover in test without raising another ESLint error. */
|
||||
if (!lineRulePermanentlyDisabled) {
|
||||
continue
|
||||
}
|
||||
returnValue.push(
|
||||
...createArrayFromTo(
|
||||
lineRulePermanentlyDisabled + 1,
|
||||
comment.loc.start.line,
|
||||
),
|
||||
)
|
||||
lineRulePermanentlyDisabled = null
|
||||
break
|
||||
/* v8 ignore next 2 -- @preserve Exhaustive guard. */
|
||||
default:
|
||||
throw new UnreachableCaseError(
|
||||
eslintDisabledRules.eslintDisableDirective,
|
||||
)
|
||||
}
|
||||
}
|
||||
return returnValue
|
||||
}
|
||||
/**
|
||||
* Creates an array of consecutive integers from start to end (inclusive).
|
||||
*
|
||||
* Helper function to generate an array of line numbers for ranges disabled by
|
||||
* eslint-disable/eslint-enable comment pairs.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* createArrayFromTo(5, 8)
|
||||
* // Returns: [5, 6, 7, 8]
|
||||
* ```
|
||||
*
|
||||
* @param i - Starting number (inclusive).
|
||||
* @param index - Ending number (inclusive).
|
||||
* @returns Array of consecutive integers from i to index.
|
||||
*/
|
||||
function createArrayFromTo(i, index) {
|
||||
return Array.from({ length: index - i + 1 }, (_, item) => i + item)
|
||||
}
|
||||
export { getEslintDisabledLines }
|
||||
Generated
Vendored
+57
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Array of all ESLint disable directive types. Used to identify and parse
|
||||
* ESLint disable comments in source code.
|
||||
*/
|
||||
declare let eslintDisableDirectives: readonly [
|
||||
'eslint-disable',
|
||||
'eslint-enable',
|
||||
'eslint-disable-line',
|
||||
'eslint-disable-next-line',
|
||||
]
|
||||
/**
|
||||
* Type representing one of the ESLint disable directive types. Can be
|
||||
* 'eslint-disable', 'eslint-enable', 'eslint-disable-line', or
|
||||
* 'eslint-disable-next-line'.
|
||||
*/
|
||||
type EslintDisableDirective = (typeof eslintDisableDirectives)[number]
|
||||
/**
|
||||
* Parses an ESLint disable comment to extract the directive type and affected
|
||||
* rules.
|
||||
*
|
||||
* Analyzes comment text to determine if it contains an ESLint disable directive
|
||||
* and which rules are affected. Returns null if the comment is not a valid
|
||||
* ESLint disable directive.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* getEslintDisabledRules('eslint-disable')
|
||||
* // Returns: { eslintDisableDirective: 'eslint-disable', rules: 'all' }
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* getEslintDisabledRules('eslint-disable-next-line no-console, no-alert')
|
||||
* // Returns: {
|
||||
* // eslintDisableDirective: 'eslint-disable-next-line',
|
||||
* // rules: ['no-console', 'no-alert']
|
||||
* // }
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* getEslintDisabledRules('regular comment')
|
||||
* // Returns: null
|
||||
* ```
|
||||
*
|
||||
* @param comment - Comment text to parse (without comment delimiters).
|
||||
* @returns Object containing directive type and affected rules, or null if not
|
||||
* a disable comment.
|
||||
*/
|
||||
export declare function getEslintDisabledRules(comment: string): {
|
||||
eslintDisableDirective: EslintDisableDirective
|
||||
rules: string[] | 'all'
|
||||
} | null
|
||||
export {}
|
||||
Generated
Vendored
+116
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* Array of all ESLint disable directive types. Used to identify and parse
|
||||
* ESLint disable comments in source code.
|
||||
*/
|
||||
var eslintDisableDirectives = [
|
||||
'eslint-disable',
|
||||
'eslint-enable',
|
||||
'eslint-disable-line',
|
||||
'eslint-disable-next-line',
|
||||
]
|
||||
/**
|
||||
* Parses an ESLint disable comment to extract the directive type and affected
|
||||
* rules.
|
||||
*
|
||||
* Analyzes comment text to determine if it contains an ESLint disable directive
|
||||
* and which rules are affected. Returns null if the comment is not a valid
|
||||
* ESLint disable directive.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* getEslintDisabledRules('eslint-disable')
|
||||
* // Returns: { eslintDisableDirective: 'eslint-disable', rules: 'all' }
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* getEslintDisabledRules('eslint-disable-next-line no-console, no-alert')
|
||||
* // Returns: {
|
||||
* // eslintDisableDirective: 'eslint-disable-next-line',
|
||||
* // rules: ['no-console', 'no-alert']
|
||||
* // }
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* getEslintDisabledRules('regular comment')
|
||||
* // Returns: null
|
||||
* ```
|
||||
*
|
||||
* @param comment - Comment text to parse (without comment delimiters).
|
||||
* @returns Object containing directive type and affected rules, or null if not
|
||||
* a disable comment.
|
||||
*/
|
||||
function getEslintDisabledRules(comment) {
|
||||
for (let eslintDisableDirective of eslintDisableDirectives) {
|
||||
let disabledRules = getEslintDisabledRulesByType(
|
||||
comment,
|
||||
eslintDisableDirective,
|
||||
)
|
||||
if (disabledRules) {
|
||||
return {
|
||||
eslintDisableDirective,
|
||||
rules: disabledRules,
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
/**
|
||||
* Extracts disabled rules from a comment for a specific ESLint directive type.
|
||||
*
|
||||
* Attempts to parse the comment as the specified ESLint disable directive.
|
||||
* Returns the list of disabled rules if the comment matches the directive,
|
||||
* 'all' if no specific rules are mentioned (global disable), or null if the
|
||||
* comment doesn't match the directive pattern.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* getEslintDisabledRulesByType('eslint-disable', 'eslint-disable')
|
||||
* // Returns: 'all'
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* getEslintDisabledRulesByType(
|
||||
* 'eslint-disable-line rule1, rule2',
|
||||
* 'eslint-disable-line',
|
||||
* )
|
||||
* // Returns: ['rule1', 'rule2']
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* getEslintDisabledRulesByType(
|
||||
* 'eslint-disable-line rule1',
|
||||
* 'eslint-disable-next-line',
|
||||
* )
|
||||
* // Returns: null (wrong directive type)
|
||||
* ```
|
||||
*
|
||||
* @param comment - Comment text to parse.
|
||||
* @param eslintDisableDirective - Specific directive type to match against.
|
||||
* @returns Array of rule names, 'all' for global disable, or null if no match.
|
||||
*/
|
||||
function getEslintDisabledRulesByType(comment, eslintDisableDirective) {
|
||||
let trimmedCommentValue = comment.trim()
|
||||
if (eslintDisableDirective === trimmedCommentValue) {
|
||||
return 'all'
|
||||
}
|
||||
let regexp = new RegExp(String.raw`^${eslintDisableDirective} ((?:.|\s)*)$`)
|
||||
let disableRulesMatchValue = trimmedCommentValue.match(regexp)?.[1]
|
||||
if (!disableRulesMatchValue) {
|
||||
return null
|
||||
}
|
||||
return disableRulesMatchValue
|
||||
.split(',')
|
||||
.map(rule => rule.trim())
|
||||
.filter(rule => !!rule)
|
||||
}
|
||||
export { getEslintDisabledRules }
|
||||
Generated
Vendored
+41
@@ -0,0 +1,41 @@
|
||||
import { GroupsOptions } from '../types/common-groups-options.js'
|
||||
import { SortingNode } from '../types/sorting-node.js'
|
||||
/**
|
||||
* Type representing a single group or an array of group names. Used in group
|
||||
* configuration where elements can belong to multiple subgroups.
|
||||
*/
|
||||
type Group = GroupsOptions[number]
|
||||
/**
|
||||
* Determines the index of the group that a node belongs to.
|
||||
*
|
||||
* Searches through the groups array to find which group contains the node.
|
||||
* Supports simple groups (string), subgroups (array of strings) and objects
|
||||
* containing a `group` property. For subgroups, the node matches if its group
|
||||
* is any element in the array.
|
||||
*
|
||||
* The function returns the index of the matching group. If no group matches, it
|
||||
* returns the length of the groups array, which conventionally represents the
|
||||
* "unknown" group and ensures such nodes are sorted last.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* const groups = ['imports', ['types', 'interfaces'], 'functions']
|
||||
* const node1 = { group: 'imports', name: 'lodash' }
|
||||
* const node2 = { group: 'types', name: 'User' }
|
||||
* const node3 = { group: 'unknown-group', name: 'misc' }
|
||||
*
|
||||
* getGroupIndex(groups, node1) // Returns: 0
|
||||
* getGroupIndex(groups, node2) // Returns: 1 (matches subgroup)
|
||||
* getGroupIndex(groups, node3) // Returns: 3 (groups.length, unknown group)
|
||||
* ```
|
||||
*
|
||||
* @param groups - Array of group configurations (strings or arrays of strings).
|
||||
* @param node - Sorting node with a group property to match.
|
||||
* @returns Index of the matching group, or groups.length if no match found.
|
||||
*/
|
||||
export declare function getGroupIndex(
|
||||
groups: Group[],
|
||||
node: SortingNode,
|
||||
): number
|
||||
export {}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
import { isGroupWithOverridesOption } from './is-group-with-overrides-option.js'
|
||||
import { isNewlinesBetweenOption } from './is-newlines-between-option.js'
|
||||
import { UnreachableCaseError } from './unreachable-case-error.js'
|
||||
/**
|
||||
* Determines the index of the group that a node belongs to.
|
||||
*
|
||||
* Searches through the groups array to find which group contains the node.
|
||||
* Supports simple groups (string), subgroups (array of strings) and objects
|
||||
* containing a `group` property. For subgroups, the node matches if its group
|
||||
* is any element in the array.
|
||||
*
|
||||
* The function returns the index of the matching group. If no group matches, it
|
||||
* returns the length of the groups array, which conventionally represents the
|
||||
* "unknown" group and ensures such nodes are sorted last.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* const groups = ['imports', ['types', 'interfaces'], 'functions']
|
||||
* const node1 = { group: 'imports', name: 'lodash' }
|
||||
* const node2 = { group: 'types', name: 'User' }
|
||||
* const node3 = { group: 'unknown-group', name: 'misc' }
|
||||
*
|
||||
* getGroupIndex(groups, node1) // Returns: 0
|
||||
* getGroupIndex(groups, node2) // Returns: 1 (matches subgroup)
|
||||
* getGroupIndex(groups, node3) // Returns: 3 (groups.length, unknown group)
|
||||
* ```
|
||||
*
|
||||
* @param groups - Array of group configurations (strings or arrays of strings).
|
||||
* @param node - Sorting node with a group property to match.
|
||||
* @returns Index of the matching group, or groups.length if no match found.
|
||||
*/
|
||||
function getGroupIndex(groups, node) {
|
||||
for (let max = groups.length, i = 0; i < max; i++) {
|
||||
let currentGroup = groups[i]
|
||||
if (doesGroupMatch(currentGroup, node.group)) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return groups.length
|
||||
}
|
||||
function doesGroupMatch(group, groupName) {
|
||||
if (typeof group === 'string' || Array.isArray(group)) {
|
||||
return doesStringGroupMatch(group, groupName)
|
||||
}
|
||||
if (isGroupWithOverridesOption(group)) {
|
||||
return doesStringGroupMatch(group.group, groupName)
|
||||
}
|
||||
/* v8 ignore else -- @preserve Exhaustive guard: other directives are filtered out earlier. */
|
||||
if (isNewlinesBetweenOption(group)) {
|
||||
return false
|
||||
}
|
||||
/* v8 ignore next -- @preserve Exhaustive guard: other directives are filtered out earlier. */
|
||||
throw new UnreachableCaseError(group)
|
||||
}
|
||||
function doesStringGroupMatch(group, groupName) {
|
||||
if (typeof group === 'string') {
|
||||
return group === groupName
|
||||
}
|
||||
return group.includes(groupName)
|
||||
}
|
||||
export { getGroupIndex }
|
||||
Generated
Vendored
+44
@@ -0,0 +1,44 @@
|
||||
import { TSESLint } from '@typescript-eslint/utils'
|
||||
import { SortingNode } from '../types/sorting-node.js'
|
||||
/**
|
||||
* Counts the number of empty lines between two AST nodes.
|
||||
*
|
||||
* Extracts the lines between the end of the left node and the start of the
|
||||
* right node, then counts only the completely empty lines (containing only
|
||||
* whitespace). This is used to determine if nodes are separated by newlines and
|
||||
* to enforce newline formatting rules.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Source code:
|
||||
* // const a = 1;
|
||||
* //
|
||||
* // const b = 2;
|
||||
*
|
||||
* getLinesBetween(sourceCode, nodeA, nodeB)
|
||||
* // Returns: 1 (one empty line between nodes)
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Source code:
|
||||
* // const a = 1;
|
||||
* // // comment
|
||||
* // const b = 2;
|
||||
*
|
||||
* getLinesBetween(sourceCode, nodeA, nodeB)
|
||||
* // Returns: 0 (no empty lines, comment line is not empty)
|
||||
* ```
|
||||
*
|
||||
* @param source - ESLint source code object containing the lines array.
|
||||
* @param left - Node or object containing the left/first node.
|
||||
* @param right - Node or object containing the right/second node.
|
||||
* @returns Number of empty lines between the two nodes.
|
||||
*/
|
||||
export declare function getLinesBetween(
|
||||
source: TSESLint.SourceCode,
|
||||
left: Pick<SortingNode, 'node'>,
|
||||
right: Pick<SortingNode, 'node'>,
|
||||
): number
|
||||
Generated
Vendored
+43
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Counts the number of empty lines between two AST nodes.
|
||||
*
|
||||
* Extracts the lines between the end of the left node and the start of the
|
||||
* right node, then counts only the completely empty lines (containing only
|
||||
* whitespace). This is used to determine if nodes are separated by newlines and
|
||||
* to enforce newline formatting rules.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Source code:
|
||||
* // const a = 1;
|
||||
* //
|
||||
* // const b = 2;
|
||||
*
|
||||
* getLinesBetween(sourceCode, nodeA, nodeB)
|
||||
* // Returns: 1 (one empty line between nodes)
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Source code:
|
||||
* // const a = 1;
|
||||
* // // comment
|
||||
* // const b = 2;
|
||||
*
|
||||
* getLinesBetween(sourceCode, nodeA, nodeB)
|
||||
* // Returns: 0 (no empty lines, comment line is not empty)
|
||||
* ```
|
||||
*
|
||||
* @param source - ESLint source code object containing the lines array.
|
||||
* @param left - Node or object containing the left/first node.
|
||||
* @param right - Node or object containing the right/second node.
|
||||
* @returns Number of empty lines between the two nodes.
|
||||
*/
|
||||
function getLinesBetween(source, left, right) {
|
||||
return source.lines
|
||||
.slice(left.node.loc.end.line, right.node.loc.start.line - 1)
|
||||
.filter(line => line.trim().length === 0).length
|
||||
}
|
||||
export { getLinesBetween }
|
||||
Generated
Vendored
+126
@@ -0,0 +1,126 @@
|
||||
import { TSESLint } from '@typescript-eslint/utils'
|
||||
import {
|
||||
NewlinesBetweenOption,
|
||||
CommonGroupsOptions,
|
||||
} from '../types/common-groups-options.js'
|
||||
import { SortingNode } from '../types/sorting-node.js'
|
||||
/**
|
||||
* Function type for customizing newlines between specific nodes.
|
||||
*
|
||||
* Allows overriding the computed newlines requirement based on the specific
|
||||
* nodes being compared. Can return 'ignore' to skip newline checking or a
|
||||
* number to specify exact newlines required.
|
||||
*
|
||||
* @template T - Type of the sorting node.
|
||||
* @param props - Properties for computing newlines.
|
||||
* @param props.computedNewlinesBetween - Default computed newlines requirement.
|
||||
* @param props.left - Left/first node.
|
||||
* @param props.right - Right/second node.
|
||||
* @returns Number of required newlines or 'ignore' to skip checking.
|
||||
*/
|
||||
export type NewlinesBetweenValueGetter<T extends SortingNode> = (props: {
|
||||
computedNewlinesBetween: NewlinesBetweenOption
|
||||
right: T
|
||||
left: T
|
||||
}) => NewlinesBetweenOption
|
||||
/**
|
||||
* Parameters for checking newlines between nodes and generating errors.
|
||||
*
|
||||
* @template MessageIds - Type of error message identifiers.
|
||||
* @template T - Type of the sorting node.
|
||||
*/
|
||||
interface GetNewlinesBetweenErrorsParameters<
|
||||
MessageIds extends string,
|
||||
T extends SortingNode,
|
||||
> {
|
||||
/**
|
||||
* Optional function to customize newlines between specific nodes.
|
||||
*/
|
||||
newlinesBetweenValueGetter?: NewlinesBetweenValueGetter<T>
|
||||
/**
|
||||
* Configuration options for newlines and groups.
|
||||
*/
|
||||
options: CommonGroupsOptions<string, unknown, unknown>
|
||||
/**
|
||||
* ESLint source code object for accessing lines.
|
||||
*/
|
||||
sourceCode: TSESLint.SourceCode
|
||||
/**
|
||||
* Error message ID for missing required newlines.
|
||||
*/
|
||||
missedSpacingError: MessageIds
|
||||
/**
|
||||
* Error message ID for extra unwanted newlines.
|
||||
*/
|
||||
extraSpacingError: MessageIds
|
||||
/**
|
||||
* Group index of the right/second node.
|
||||
*/
|
||||
rightGroupIndex: number
|
||||
/**
|
||||
* Group index of the left/first node.
|
||||
*/
|
||||
leftGroupIndex: number
|
||||
/**
|
||||
* Right/second node in the comparison.
|
||||
*/
|
||||
right: T
|
||||
/**
|
||||
* Left/first node in the comparison.
|
||||
*/
|
||||
left: T
|
||||
}
|
||||
/**
|
||||
* Checks if the newlines between two nodes match the required configuration.
|
||||
*
|
||||
* Validates the number of empty lines between two nodes against the expected
|
||||
* newlines based on their group indices and configuration. Generates
|
||||
* appropriate error messages when the actual newlines don't match the
|
||||
* requirement.
|
||||
*
|
||||
* The function returns no errors if:
|
||||
*
|
||||
* - The left node's group index is greater than the right's (wrong order)
|
||||
* - The nodes are in different partitions
|
||||
* - The newlines configuration is set to 'ignore'
|
||||
* - The actual newlines match the expected newlines.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Configuration requires 1 newline between different groups
|
||||
* const errors = getNewlinesBetweenErrors({
|
||||
* options: { newlinesBetween: 1, groups: ['imports', 'types'] },
|
||||
* leftGroupIndex: 0, // imports group
|
||||
* rightGroupIndex: 1, // types group
|
||||
* left: importNode,
|
||||
* right: typeNode,
|
||||
* sourceCode,
|
||||
* missedSpacingError: 'missedNewline',
|
||||
* extraSpacingError: 'extraNewline',
|
||||
* })
|
||||
* // If no newline between nodes: Returns ['missedNewline']
|
||||
* // If 2+ newlines between nodes: Returns ['extraNewline']
|
||||
* // If exactly 1 newline: Returns []
|
||||
* ```
|
||||
*
|
||||
* @template MessageIds - Type of error message identifiers.
|
||||
* @template T - Type of the sorting node.
|
||||
* @param params - Parameters for newline checking.
|
||||
* @returns Array of error message IDs (empty if no errors).
|
||||
*/
|
||||
export declare function getNewlinesBetweenErrors<
|
||||
MessageIds extends string,
|
||||
T extends SortingNode,
|
||||
>({
|
||||
newlinesBetweenValueGetter,
|
||||
missedSpacingError,
|
||||
extraSpacingError,
|
||||
rightGroupIndex,
|
||||
leftGroupIndex,
|
||||
sourceCode,
|
||||
options,
|
||||
right,
|
||||
left,
|
||||
}: GetNewlinesBetweenErrorsParameters<MessageIds, T>): MessageIds[]
|
||||
export {}
|
||||
Generated
Vendored
+82
@@ -0,0 +1,82 @@
|
||||
import { getNewlinesBetweenOption } from './get-newlines-between-option.js'
|
||||
import { getLinesBetween } from './get-lines-between.js'
|
||||
/**
|
||||
* Checks if the newlines between two nodes match the required configuration.
|
||||
*
|
||||
* Validates the number of empty lines between two nodes against the expected
|
||||
* newlines based on their group indices and configuration. Generates
|
||||
* appropriate error messages when the actual newlines don't match the
|
||||
* requirement.
|
||||
*
|
||||
* The function returns no errors if:
|
||||
*
|
||||
* - The left node's group index is greater than the right's (wrong order)
|
||||
* - The nodes are in different partitions
|
||||
* - The newlines configuration is set to 'ignore'
|
||||
* - The actual newlines match the expected newlines.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Configuration requires 1 newline between different groups
|
||||
* const errors = getNewlinesBetweenErrors({
|
||||
* options: { newlinesBetween: 1, groups: ['imports', 'types'] },
|
||||
* leftGroupIndex: 0, // imports group
|
||||
* rightGroupIndex: 1, // types group
|
||||
* left: importNode,
|
||||
* right: typeNode,
|
||||
* sourceCode,
|
||||
* missedSpacingError: 'missedNewline',
|
||||
* extraSpacingError: 'extraNewline',
|
||||
* })
|
||||
* // If no newline between nodes: Returns ['missedNewline']
|
||||
* // If 2+ newlines between nodes: Returns ['extraNewline']
|
||||
* // If exactly 1 newline: Returns []
|
||||
* ```
|
||||
*
|
||||
* @template MessageIds - Type of error message identifiers.
|
||||
* @template T - Type of the sorting node.
|
||||
* @param params - Parameters for newline checking.
|
||||
* @returns Array of error message IDs (empty if no errors).
|
||||
*/
|
||||
function getNewlinesBetweenErrors({
|
||||
newlinesBetweenValueGetter,
|
||||
missedSpacingError,
|
||||
extraSpacingError,
|
||||
rightGroupIndex,
|
||||
leftGroupIndex,
|
||||
sourceCode,
|
||||
options,
|
||||
right,
|
||||
left,
|
||||
}) {
|
||||
if (
|
||||
leftGroupIndex > rightGroupIndex ||
|
||||
left.partitionId !== right.partitionId
|
||||
) {
|
||||
return []
|
||||
}
|
||||
let newlinesBetween = getNewlinesBetweenOption({
|
||||
nextNodeGroupIndex: rightGroupIndex,
|
||||
nodeGroupIndex: leftGroupIndex,
|
||||
options,
|
||||
})
|
||||
newlinesBetween =
|
||||
newlinesBetweenValueGetter?.({
|
||||
computedNewlinesBetween: newlinesBetween,
|
||||
right,
|
||||
left,
|
||||
}) ?? newlinesBetween
|
||||
if (newlinesBetween === 'ignore') {
|
||||
return []
|
||||
}
|
||||
let numberOfEmptyLinesBetween = getLinesBetween(sourceCode, left, right)
|
||||
if (numberOfEmptyLinesBetween < newlinesBetween) {
|
||||
return [missedSpacingError]
|
||||
}
|
||||
if (numberOfEmptyLinesBetween > newlinesBetween) {
|
||||
return [extraSpacingError]
|
||||
}
|
||||
return []
|
||||
}
|
||||
export { getNewlinesBetweenErrors }
|
||||
Generated
Vendored
+43
@@ -0,0 +1,43 @@
|
||||
import {
|
||||
NewlinesBetweenOption,
|
||||
CommonGroupsOptions,
|
||||
} from '../types/common-groups-options.js'
|
||||
/**
|
||||
* Parameters for determining newlines requirement between nodes.
|
||||
*
|
||||
* Contains group indices and configuration options needed to calculate the
|
||||
* required number of newlines between two nodes.
|
||||
*/
|
||||
export interface GetNewlinesBetweenOptionParameters {
|
||||
/**
|
||||
* Configuration options for newlines and groups.
|
||||
*/
|
||||
options: CommonGroupsOptions<string, unknown, unknown>
|
||||
/**
|
||||
* Group index of the next/second node.
|
||||
*/
|
||||
nextNodeGroupIndex: number
|
||||
/**
|
||||
* Group index of the current/first node.
|
||||
*/
|
||||
nodeGroupIndex: number
|
||||
}
|
||||
/**
|
||||
* Get the `newlinesBetween` option to use between two consecutive nodes. The
|
||||
* result is based on the global `newlinesBetween` option and the custom groups,
|
||||
* which can override the global option.
|
||||
*
|
||||
* - If the two nodes are in the same custom group, the `newlinesInside` option of
|
||||
* the group is used.
|
||||
*
|
||||
* @param props - The function arguments.
|
||||
* @param props.nextNodeGroupIndex - The next node index to sort.
|
||||
* @param props.nodeGroupIndex - The current node index to sort.
|
||||
* @param props.options - Newlines between related options.
|
||||
* @returns - The `newlinesBetween` option to use.
|
||||
*/
|
||||
export declare function getNewlinesBetweenOption({
|
||||
nextNodeGroupIndex,
|
||||
nodeGroupIndex,
|
||||
options,
|
||||
}: GetNewlinesBetweenOptionParameters): NewlinesBetweenOption
|
||||
Generated
Vendored
+145
@@ -0,0 +1,145 @@
|
||||
import { isGroupWithOverridesOption } from './is-group-with-overrides-option.js'
|
||||
import { isNewlinesBetweenOption } from './is-newlines-between-option.js'
|
||||
import { computeGroupName } from './compute-group-name.js'
|
||||
/**
|
||||
* Get the `newlinesBetween` option to use between two consecutive nodes. The
|
||||
* result is based on the global `newlinesBetween` option and the custom groups,
|
||||
* which can override the global option.
|
||||
*
|
||||
* - If the two nodes are in the same custom group, the `newlinesInside` option of
|
||||
* the group is used.
|
||||
*
|
||||
* @param props - The function arguments.
|
||||
* @param props.nextNodeGroupIndex - The next node index to sort.
|
||||
* @param props.nodeGroupIndex - The current node index to sort.
|
||||
* @param props.options - Newlines between related options.
|
||||
* @returns - The `newlinesBetween` option to use.
|
||||
*/
|
||||
function getNewlinesBetweenOption({
|
||||
nextNodeGroupIndex,
|
||||
nodeGroupIndex,
|
||||
options,
|
||||
}) {
|
||||
if (nodeGroupIndex === nextNodeGroupIndex) {
|
||||
return computeNewlinesInsideOption({
|
||||
groupIndex: nodeGroupIndex,
|
||||
options,
|
||||
})
|
||||
}
|
||||
if (nextNodeGroupIndex >= nodeGroupIndex + 2) {
|
||||
return computeNewlinesBetweenOptionForDifferentGroups({
|
||||
nextNodeGroupIndex,
|
||||
nodeGroupIndex,
|
||||
options,
|
||||
})
|
||||
}
|
||||
return options.newlinesBetween
|
||||
}
|
||||
function computeNewlinesBetweenOptionForDifferentGroups({
|
||||
nextNodeGroupIndex,
|
||||
nodeGroupIndex,
|
||||
options,
|
||||
}) {
|
||||
if (nextNodeGroupIndex === nodeGroupIndex + 2) {
|
||||
let groupBetween = options.groups[nodeGroupIndex + 1]
|
||||
if (isNewlinesBetweenOption(groupBetween)) {
|
||||
return groupBetween.newlinesBetween
|
||||
}
|
||||
return options.newlinesBetween
|
||||
}
|
||||
let groupsWithAllNewlinesBetween = buildGroupsWithAllNewlinesBetween(
|
||||
options.groups.slice(nodeGroupIndex, nextNodeGroupIndex + 1),
|
||||
options.newlinesBetween,
|
||||
)
|
||||
let newlinesBetweenOptions = new Set(
|
||||
groupsWithAllNewlinesBetween
|
||||
.filter(isNewlinesBetweenOption)
|
||||
.map(group => group.newlinesBetween),
|
||||
)
|
||||
let numberNewlinesBetween = [...newlinesBetweenOptions].filter(
|
||||
option => typeof option === 'number',
|
||||
)
|
||||
let maxNewlinesBetween =
|
||||
numberNewlinesBetween.length > 0 ? Math.max(...numberNewlinesBetween) : null
|
||||
if (maxNewlinesBetween !== null && maxNewlinesBetween >= 1) {
|
||||
return maxNewlinesBetween
|
||||
}
|
||||
if (newlinesBetweenOptions.has('ignore')) {
|
||||
return 'ignore'
|
||||
}
|
||||
return 0
|
||||
}
|
||||
function computeNewlinesInsideOption({ groupIndex, options }) {
|
||||
let globalNewlinesInsideOption = computeGlobalNewlinesInsideOption()
|
||||
let group = options.groups[groupIndex]
|
||||
if (!group) {
|
||||
return globalNewlinesInsideOption
|
||||
}
|
||||
let groupName = computeGroupName(group)
|
||||
let nodeCustomGroup = options.customGroups.find(
|
||||
customGroup => customGroup.groupName === groupName,
|
||||
)
|
||||
let groupOverrideNewlinesInside =
|
||||
isGroupWithOverridesOption(group) ? group.newlinesInside : null
|
||||
return (
|
||||
nodeCustomGroup?.newlinesInside ??
|
||||
groupOverrideNewlinesInside ??
|
||||
globalNewlinesInsideOption
|
||||
)
|
||||
function computeGlobalNewlinesInsideOption() {
|
||||
switch (options.newlinesInside) {
|
||||
case 'newlinesBetween':
|
||||
return options.newlinesBetween === 'ignore' ? 'ignore' : 0
|
||||
case 'ignore':
|
||||
return 'ignore'
|
||||
default:
|
||||
return options.newlinesInside
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Inserts newlines settings between groups that don't already have them.
|
||||
*
|
||||
* Fills in missing newlines settings between adjacent groups using the global
|
||||
* newlines option. This ensures every transition between groups has an explicit
|
||||
* newlines setting for consistent calculation.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* buildGroupsWithAllNewlinesBetween(
|
||||
* ['imports', 'types', { newlinesBetween: 2 }, 'functions'],
|
||||
* 1,
|
||||
* )
|
||||
* // Returns: [
|
||||
* // 'imports',
|
||||
* // { newlinesBetween: 1 }, // Added
|
||||
* // 'types',
|
||||
* // { newlinesBetween: 2 }, // Already existed
|
||||
* // 'functions'
|
||||
* // ]
|
||||
* ```
|
||||
*
|
||||
* @param groups - Array of groups with optional inline newlines settings.
|
||||
* @param globalNewlinesBetweenOption - Default newlines to use for missing
|
||||
* settings.
|
||||
* @returns Groups array with newlines settings filled in between all groups.
|
||||
*/
|
||||
function buildGroupsWithAllNewlinesBetween(
|
||||
groups,
|
||||
globalNewlinesBetweenOption,
|
||||
) {
|
||||
let returnValue = []
|
||||
for (let i = 0; i < groups.length; i++) {
|
||||
let group = groups[i]
|
||||
if (!isNewlinesBetweenOption(group)) {
|
||||
let previousGroup = groups[i - 1]
|
||||
if (previousGroup && !isNewlinesBetweenOption(previousGroup)) {
|
||||
returnValue.push({ newlinesBetween: globalNewlinesBetweenOption })
|
||||
}
|
||||
}
|
||||
returnValue.push(group)
|
||||
}
|
||||
return returnValue
|
||||
}
|
||||
export { getNewlinesBetweenOption }
|
||||
Generated
Vendored
+33
@@ -0,0 +1,33 @@
|
||||
import { TSESTree } from '@typescript-eslint/types'
|
||||
/**
|
||||
* Type representing an AST node that may have decorators.
|
||||
*
|
||||
* Extends the base Node type with an optional decorators array. Used for
|
||||
* TypeScript/JavaScript decorators on classes, methods, and properties.
|
||||
*/
|
||||
type NodeWithDecorator = {
|
||||
decorators: TSESTree.Decorator[]
|
||||
} & TSESTree.Node
|
||||
/**
|
||||
* Safely retrieves decorators from an AST node.
|
||||
*
|
||||
* Provides a safe way to access the decorators property which may not exist on
|
||||
* all nodes or in all parser versions. Returns an empty array when decorators
|
||||
* are undefined, ensuring consistent behavior across different AST structures.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Node without decorators
|
||||
* const plainMethod = { type: 'MethodDefinition', ... };
|
||||
* getNodeDecorators(plainMethod);
|
||||
* // Returns: []
|
||||
* ```
|
||||
*
|
||||
* @param node - AST node that may contain decorators.
|
||||
* @returns Array of decorator nodes, empty array if none exist.
|
||||
*/
|
||||
export declare function getNodeDecorators(
|
||||
node: NodeWithDecorator,
|
||||
): TSESTree.Decorator[]
|
||||
export {}
|
||||
Generated
Vendored
+23
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Safely retrieves decorators from an AST node.
|
||||
*
|
||||
* Provides a safe way to access the decorators property which may not exist on
|
||||
* all nodes or in all parser versions. Returns an empty array when decorators
|
||||
* are undefined, ensuring consistent behavior across different AST structures.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Node without decorators
|
||||
* const plainMethod = { type: 'MethodDefinition', ... };
|
||||
* getNodeDecorators(plainMethod);
|
||||
* // Returns: []
|
||||
* ```
|
||||
*
|
||||
* @param node - AST node that may contain decorators.
|
||||
* @returns Array of decorator nodes, empty array if none exist.
|
||||
*/
|
||||
function getNodeDecorators(node) {
|
||||
return node.decorators ?? []
|
||||
}
|
||||
export { getNodeDecorators }
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
import { TSESTree } from '@typescript-eslint/types'
|
||||
import { TSESLint } from '@typescript-eslint/utils'
|
||||
import { CommonPartitionOptions } from '../types/common-partition-options.js'
|
||||
/**
|
||||
* Parameters for determining the complete range of a node.
|
||||
*
|
||||
* Configures how to calculate the node's range including associated comments
|
||||
* and parentheses.
|
||||
*/
|
||||
interface GetNodeRangeParameters {
|
||||
/**
|
||||
* Optional configuration for comment handling.
|
||||
*/
|
||||
options?: Pick<CommonPartitionOptions, 'partitionByComment'>
|
||||
/**
|
||||
* Whether to exclude the highest-level block comment from the range. Useful
|
||||
* for preserving file-level documentation comments in their original
|
||||
* position.
|
||||
*/
|
||||
ignoreHighestBlockComment?: boolean
|
||||
/**
|
||||
* ESLint source code object for accessing comments and tokens.
|
||||
*/
|
||||
sourceCode: TSESLint.SourceCode
|
||||
/**
|
||||
* AST node to get the range for.
|
||||
*/
|
||||
node: TSESTree.Node
|
||||
}
|
||||
/**
|
||||
* Determines the complete range of a node including its associated comments.
|
||||
*
|
||||
* Calculates the full range that should be considered when moving or analyzing
|
||||
* a node. This includes:
|
||||
*
|
||||
* - The node itself
|
||||
* - Parentheses surrounding the node (if any)
|
||||
* - Preceding comments that "belong" to the node.
|
||||
*
|
||||
* The function intelligently determines which comments should be included by:
|
||||
*
|
||||
* - Including comments directly above the node (no empty lines between)
|
||||
* - Stopping at partition comments (used to separate sections)
|
||||
* - Stopping at ESLint disable/enable comments
|
||||
* - Optionally excluding the highest block comment (e.g., file headers).
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Source code:
|
||||
* // This comment belongs to the function
|
||||
* // So does this one
|
||||
* function foo() {}
|
||||
*
|
||||
* const range = getNodeRange({ node: functionNode, sourceCode })
|
||||
* // Returns range including both comments
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Source code:
|
||||
* /* File header comment *\/
|
||||
* // Function comment
|
||||
* function bar() { }
|
||||
*
|
||||
* const range = getNodeRange({
|
||||
* node: functionNode,
|
||||
* sourceCode,
|
||||
* ignoreHighestBlockComment: true
|
||||
* });
|
||||
* // Returns range including line comment but not block comment
|
||||
* ```
|
||||
*
|
||||
* @param params - Parameters for range calculation.
|
||||
* @returns Tuple of [start, end] positions including relevant comments.
|
||||
*/
|
||||
export declare function getNodeRange({
|
||||
ignoreHighestBlockComment,
|
||||
sourceCode,
|
||||
options,
|
||||
node,
|
||||
}: GetNodeRangeParameters): TSESTree.Range
|
||||
export {}
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
import { getEslintDisabledRules } from './get-eslint-disabled-rules.js'
|
||||
import { isPartitionComment } from './is-partition-comment.js'
|
||||
import { getCommentsBefore } from './get-comments-before.js'
|
||||
import { ASTUtils } from '@typescript-eslint/utils'
|
||||
/**
|
||||
* Determines the complete range of a node including its associated comments.
|
||||
*
|
||||
* Calculates the full range that should be considered when moving or analyzing
|
||||
* a node. This includes:
|
||||
*
|
||||
* - The node itself
|
||||
* - Parentheses surrounding the node (if any)
|
||||
* - Preceding comments that "belong" to the node.
|
||||
*
|
||||
* The function intelligently determines which comments should be included by:
|
||||
*
|
||||
* - Including comments directly above the node (no empty lines between)
|
||||
* - Stopping at partition comments (used to separate sections)
|
||||
* - Stopping at ESLint disable/enable comments
|
||||
* - Optionally excluding the highest block comment (e.g., file headers).
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Source code:
|
||||
* // This comment belongs to the function
|
||||
* // So does this one
|
||||
* function foo() {}
|
||||
*
|
||||
* const range = getNodeRange({ node: functionNode, sourceCode })
|
||||
* // Returns range including both comments
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Source code:
|
||||
* /* File header comment *\/
|
||||
* // Function comment
|
||||
* function bar() { }
|
||||
*
|
||||
* const range = getNodeRange({
|
||||
* node: functionNode,
|
||||
* sourceCode,
|
||||
* ignoreHighestBlockComment: true
|
||||
* });
|
||||
* // Returns range including line comment but not block comment
|
||||
* ```
|
||||
*
|
||||
* @param params - Parameters for range calculation.
|
||||
* @returns Tuple of [start, end] positions including relevant comments.
|
||||
*/
|
||||
function getNodeRange({
|
||||
ignoreHighestBlockComment,
|
||||
sourceCode,
|
||||
options,
|
||||
node,
|
||||
}) {
|
||||
let start = node.range.at(0)
|
||||
let end = node.range.at(1)
|
||||
if (ASTUtils.isParenthesized(node, sourceCode)) {
|
||||
let bodyOpeningParen = sourceCode.getTokenBefore(
|
||||
node,
|
||||
ASTUtils.isOpeningParenToken,
|
||||
)
|
||||
let bodyClosingParen = sourceCode.getTokenAfter(
|
||||
node,
|
||||
ASTUtils.isClosingParenToken,
|
||||
)
|
||||
start = bodyOpeningParen.range.at(0)
|
||||
end = bodyClosingParen.range.at(1)
|
||||
}
|
||||
let comments = getCommentsBefore({
|
||||
sourceCode,
|
||||
node,
|
||||
})
|
||||
let highestBlockComment = comments.find(comment => comment.type === 'Block')
|
||||
/**
|
||||
* Iterate on all comments starting from the bottom until we reach the last of
|
||||
* the comments, a newline between comments, a partition comment, or a
|
||||
* eslint-disable comment.
|
||||
*/
|
||||
let relevantTopComment
|
||||
for (let i = comments.length - 1; i >= 0; i--) {
|
||||
let comment = comments[i]
|
||||
let eslintDisabledRules = getEslintDisabledRules(comment.value)
|
||||
if (
|
||||
isPartitionComment({
|
||||
partitionByComment: options?.partitionByComment ?? false,
|
||||
comment,
|
||||
}) ||
|
||||
eslintDisabledRules?.eslintDisableDirective === 'eslint-disable' ||
|
||||
eslintDisabledRules?.eslintDisableDirective === 'eslint-enable'
|
||||
) {
|
||||
break
|
||||
}
|
||||
/**
|
||||
* Check for newlines between comments or between the first comment and the
|
||||
* node.
|
||||
*/
|
||||
let previousCommentOrNodeStartLine =
|
||||
i === comments.length - 1 ?
|
||||
node.loc.start.line
|
||||
: comments[i + 1].loc.start.line
|
||||
if (comment.loc.end.line !== previousCommentOrNodeStartLine - 1) {
|
||||
break
|
||||
}
|
||||
if (ignoreHighestBlockComment && comment === highestBlockComment) {
|
||||
break
|
||||
}
|
||||
relevantTopComment = comment
|
||||
}
|
||||
if (relevantTopComment) {
|
||||
start = relevantTopComment.range.at(0)
|
||||
}
|
||||
return [start, end]
|
||||
}
|
||||
export { getNodeRange }
|
||||
Generated
Vendored
+58
@@ -0,0 +1,58 @@
|
||||
import { GroupsOptions } from '../types/common-groups-options.js'
|
||||
/**
|
||||
* Parameters for cleaning and normalizing groups configuration.
|
||||
*
|
||||
* Contains the groups array that needs to be cleaned up.
|
||||
*/
|
||||
interface GetOptionsWithCleanGroupsParameters<CustomTypeOption extends string> {
|
||||
/**
|
||||
* Groups configuration that may contain empty arrays or single-element
|
||||
* arrays.
|
||||
*/
|
||||
groups: GroupsOptions<CustomTypeOption>
|
||||
}
|
||||
/**
|
||||
* Cleans and normalizes the groups configuration in options.
|
||||
*
|
||||
* Performs the following optimizations on the groups array:
|
||||
*
|
||||
* - Removes empty arrays (they serve no purpose in grouping)
|
||||
* - Converts single-element arrays to plain strings (simplifies structure)
|
||||
* - Preserves multi-element arrays as-is (maintains subgroups).
|
||||
*
|
||||
* This normalization ensures consistent group handling and eliminates
|
||||
* unnecessary complexity in the configuration.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* getOptionsWithCleanGroups({
|
||||
* groups: [
|
||||
* 'imports',
|
||||
* ['types'], // Single element - will become 'types'
|
||||
* ['hooks', 'utils'], // Multiple elements - preserved as array
|
||||
* [], // Empty - will be removed
|
||||
* 'components',
|
||||
* ],
|
||||
* })
|
||||
* // Returns: {
|
||||
* // groups: [
|
||||
* // 'imports',
|
||||
* // 'types',
|
||||
* // ['hooks', 'utils'],
|
||||
* // 'components'
|
||||
* // ]
|
||||
* // }
|
||||
* ```
|
||||
*
|
||||
* @template CustomTypeOption - Custom type option string for GroupsOptions.
|
||||
* @template Options - Type of options extending
|
||||
* GetOptionsWithCleanGroupsParameters.
|
||||
* @param options - Options object containing groups to clean.
|
||||
* @returns Options with cleaned and normalized groups array.
|
||||
*/
|
||||
export declare function getOptionsWithCleanGroups<
|
||||
CustomTypeOption extends string,
|
||||
Options extends GetOptionsWithCleanGroupsParameters<CustomTypeOption>,
|
||||
>(options: Options): Options
|
||||
export {}
|
||||
Generated
Vendored
+78
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* Cleans and normalizes the groups configuration in options.
|
||||
*
|
||||
* Performs the following optimizations on the groups array:
|
||||
*
|
||||
* - Removes empty arrays (they serve no purpose in grouping)
|
||||
* - Converts single-element arrays to plain strings (simplifies structure)
|
||||
* - Preserves multi-element arrays as-is (maintains subgroups).
|
||||
*
|
||||
* This normalization ensures consistent group handling and eliminates
|
||||
* unnecessary complexity in the configuration.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* getOptionsWithCleanGroups({
|
||||
* groups: [
|
||||
* 'imports',
|
||||
* ['types'], // Single element - will become 'types'
|
||||
* ['hooks', 'utils'], // Multiple elements - preserved as array
|
||||
* [], // Empty - will be removed
|
||||
* 'components',
|
||||
* ],
|
||||
* })
|
||||
* // Returns: {
|
||||
* // groups: [
|
||||
* // 'imports',
|
||||
* // 'types',
|
||||
* // ['hooks', 'utils'],
|
||||
* // 'components'
|
||||
* // ]
|
||||
* // }
|
||||
* ```
|
||||
*
|
||||
* @template CustomTypeOption - Custom type option string for GroupsOptions.
|
||||
* @template Options - Type of options extending
|
||||
* GetOptionsWithCleanGroupsParameters.
|
||||
* @param options - Options object containing groups to clean.
|
||||
* @returns Options with cleaned and normalized groups array.
|
||||
*/
|
||||
function getOptionsWithCleanGroups(options) {
|
||||
return {
|
||||
...options,
|
||||
groups: options.groups
|
||||
.filter(group => !Array.isArray(group) || group.length > 0)
|
||||
.map(group =>
|
||||
Array.isArray(group) ? getCleanedNestedGroups(group) : group,
|
||||
),
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Simplifies a nested group array by converting single-element arrays to
|
||||
* strings.
|
||||
*
|
||||
* Helper function that normalizes array groups:
|
||||
*
|
||||
* - Single-element arrays are unwrapped to plain strings
|
||||
* - Multi-element arrays are preserved as-is
|
||||
* - Empty single-element arrays remain as arrays.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* getCleanedNestedGroups(['types']) // Returns: 'types'
|
||||
* getCleanedNestedGroups(['a', 'b']) // Returns: ['a', 'b']
|
||||
* getCleanedNestedGroups(['']) // Returns: ['']
|
||||
* ```
|
||||
*
|
||||
* @param nestedGroup - Array of group names to potentially simplify.
|
||||
* @returns Simplified string if single non-empty element, otherwise the
|
||||
* original array.
|
||||
*/
|
||||
function getCleanedNestedGroups(nestedGroup) {
|
||||
return nestedGroup.length === 1 && nestedGroup[0] ?
|
||||
nestedGroup[0]
|
||||
: nestedGroup
|
||||
}
|
||||
export { getOptionsWithCleanGroups }
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
import { TSESLint } from '@typescript-eslint/utils'
|
||||
import { CommonPartitionOptions } from '../types/common-partition-options.js'
|
||||
import { CommonGroupsOptions } from '../types/common-groups-options.js'
|
||||
import { CommonOptions } from '../types/common-options.js'
|
||||
/**
|
||||
* Global settings for the Perfectionist plugin.
|
||||
*
|
||||
* These settings can be configured in ESLint configuration under the
|
||||
* 'perfectionist' key and apply to all Perfectionist rules unless overridden by
|
||||
* rule-specific options.
|
||||
*/
|
||||
export type Settings = Partial<
|
||||
Pick<
|
||||
CommonGroupsOptions<string, unknown, unknown>,
|
||||
'newlinesBetween' | 'newlinesInside'
|
||||
> &
|
||||
CommonPartitionOptions &
|
||||
CommonOptions
|
||||
>
|
||||
/**
|
||||
* Extracts and validates Perfectionist settings from ESLint configuration.
|
||||
*
|
||||
* Retrieves global Perfectionist settings that apply to all rules. Validates
|
||||
* that only allowed settings are provided and throws an error if invalid
|
||||
* options are detected. This ensures configuration errors are caught early with
|
||||
* clear error messages.
|
||||
*
|
||||
* The settings are accessed from the 'perfectionist' key in ESLint's shared
|
||||
* configuration settings.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Valid usage:
|
||||
* const settings = getSettings(context.settings)
|
||||
* // Returns: { type: 'natural', order: 'asc', ignoreCase: true }
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Invalid setting throws error:
|
||||
* getSettings({
|
||||
* perfectionist: {
|
||||
* type: 'natural',
|
||||
* invalidOption: true, // This will throw
|
||||
* },
|
||||
* })
|
||||
* // Throws: Error: Invalid Perfectionist setting(s): invalidOption
|
||||
* ```
|
||||
*
|
||||
* @param settings - ESLint shared configuration settings object.
|
||||
* @returns Validated Perfectionist settings or empty object if none configured.
|
||||
* @throws {Error} If invalid settings are provided.
|
||||
*/
|
||||
export declare function getSettings(
|
||||
settings: TSESLint.SharedConfigurationSettings,
|
||||
): Settings
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Extracts and validates Perfectionist settings from ESLint configuration.
|
||||
*
|
||||
* Retrieves global Perfectionist settings that apply to all rules. Validates
|
||||
* that only allowed settings are provided and throws an error if invalid
|
||||
* options are detected. This ensures configuration errors are caught early with
|
||||
* clear error messages.
|
||||
*
|
||||
* The settings are accessed from the 'perfectionist' key in ESLint's shared
|
||||
* configuration settings.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Valid usage:
|
||||
* const settings = getSettings(context.settings)
|
||||
* // Returns: { type: 'natural', order: 'asc', ignoreCase: true }
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Invalid setting throws error:
|
||||
* getSettings({
|
||||
* perfectionist: {
|
||||
* type: 'natural',
|
||||
* invalidOption: true, // This will throw
|
||||
* },
|
||||
* })
|
||||
* // Throws: Error: Invalid Perfectionist setting(s): invalidOption
|
||||
* ```
|
||||
*
|
||||
* @param settings - ESLint shared configuration settings object.
|
||||
* @returns Validated Perfectionist settings or empty object if none configured.
|
||||
* @throws {Error} If invalid settings are provided.
|
||||
*/
|
||||
function getSettings(settings) {
|
||||
if (!settings['perfectionist']) {
|
||||
return {}
|
||||
}
|
||||
/**
|
||||
* Identifies settings keys that are not in the allowed list.
|
||||
*
|
||||
* @param object - Settings object to validate.
|
||||
* @returns Array of invalid setting names.
|
||||
*/
|
||||
function getInvalidOptions(object) {
|
||||
let allowedOptions = new Set([
|
||||
'partitionByComment',
|
||||
'partitionByNewLine',
|
||||
'specialCharacters',
|
||||
'newlinesBetween',
|
||||
'newlinesInside',
|
||||
'fallbackSort',
|
||||
'ignoreCase',
|
||||
'alphabet',
|
||||
'locales',
|
||||
'order',
|
||||
'type',
|
||||
])
|
||||
return Object.keys(object).filter(key => !allowedOptions.has(key))
|
||||
}
|
||||
let perfectionistSettings = settings['perfectionist']
|
||||
let invalidOptions = getInvalidOptions(perfectionistSettings)
|
||||
if (invalidOptions.length > 0) {
|
||||
throw new Error(
|
||||
`Invalid Perfectionist setting(s): ${invalidOptions.join(', ')}`,
|
||||
)
|
||||
}
|
||||
return settings['perfectionist']
|
||||
}
|
||||
export { getSettings }
|
||||
Generated
Vendored
+40
@@ -0,0 +1,40 @@
|
||||
import {
|
||||
GroupWithOverridesOption,
|
||||
GroupsOptions,
|
||||
} from '../types/common-groups-options.js'
|
||||
/**
|
||||
* Type guard to check if a group option is a group with overrides
|
||||
* configuration.
|
||||
*
|
||||
* Determines whether a group element is a special configuration object that
|
||||
* contains group settings rather than being a regular group name or newlines
|
||||
* option.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* const groups = [
|
||||
* 'imports',
|
||||
* { group: 'foo', commentAbove: '// Components' },
|
||||
* 'components',
|
||||
* { newlinesBetween: 1 },
|
||||
* 'utils',
|
||||
* ]
|
||||
*
|
||||
* isGroupWithOverridesOption(groups[0]) // false (string)
|
||||
* isGroupWithOverridesOption(groups[1]) // true
|
||||
* isGroupWithOverridesOption(groups[3]) // false (newlines option)
|
||||
* ```
|
||||
*
|
||||
* @param groupOption - A single element from the groups configuration array.
|
||||
* @returns True if the element is a group with overrides configuration object.
|
||||
*/
|
||||
export declare function isGroupWithOverridesOption<
|
||||
CustomTypeOption extends string,
|
||||
AdditionalSortOptions,
|
||||
>(
|
||||
groupOption: GroupsOptions<CustomTypeOption, AdditionalSortOptions>[number],
|
||||
): groupOption is GroupWithOverridesOption<
|
||||
CustomTypeOption,
|
||||
AdditionalSortOptions
|
||||
>
|
||||
Generated
Vendored
+31
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Type guard to check if a group option is a group with overrides
|
||||
* configuration.
|
||||
*
|
||||
* Determines whether a group element is a special configuration object that
|
||||
* contains group settings rather than being a regular group name or newlines
|
||||
* option.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* const groups = [
|
||||
* 'imports',
|
||||
* { group: 'foo', commentAbove: '// Components' },
|
||||
* 'components',
|
||||
* { newlinesBetween: 1 },
|
||||
* 'utils',
|
||||
* ]
|
||||
*
|
||||
* isGroupWithOverridesOption(groups[0]) // false (string)
|
||||
* isGroupWithOverridesOption(groups[1]) // true
|
||||
* isGroupWithOverridesOption(groups[3]) // false (newlines option)
|
||||
* ```
|
||||
*
|
||||
* @param groupOption - A single element from the groups configuration array.
|
||||
* @returns True if the element is a group with overrides configuration object.
|
||||
*/
|
||||
function isGroupWithOverridesOption(groupOption) {
|
||||
return typeof groupOption === 'object' && 'group' in groupOption
|
||||
}
|
||||
export { isGroupWithOverridesOption }
|
||||
Generated
Vendored
+40
@@ -0,0 +1,40 @@
|
||||
import {
|
||||
GroupNewlinesBetweenOption,
|
||||
GroupsOptions,
|
||||
} from '../types/common-groups-options.js'
|
||||
/**
|
||||
* Type guard to check if a group option is a newlines-between configuration.
|
||||
*
|
||||
* Determines whether a group element contains a `newlinesBetween` property,
|
||||
* which indicates it's a special configuration object that controls spacing
|
||||
* between groups rather than being a regular group name or comment option.
|
||||
*
|
||||
* Newlines-between options are placed between group names in the configuration
|
||||
* to specify how many newlines should separate those groups in the sorted
|
||||
* output.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* const groups = [
|
||||
* 'imports',
|
||||
* { newlinesBetween: 1 }, // Add 1 newline between imports and types
|
||||
* 'types',
|
||||
* { newlinesBetween: 2 }, // Add 2 newlines between types and components
|
||||
* 'components',
|
||||
* { commentAbove: '// Utils' }, // Not a newlines option
|
||||
* 'utils',
|
||||
* ]
|
||||
*
|
||||
* isNewlinesBetweenOption(groups[0]) // false (string)
|
||||
* isNewlinesBetweenOption(groups[1]) // true (has newlinesBetween)
|
||||
* isNewlinesBetweenOption(groups[3]) // true (has newlinesBetween)
|
||||
* isNewlinesBetweenOption(groups[5]) // false (comment option)
|
||||
* ```
|
||||
*
|
||||
* @param groupOption - A single element from the groups configuration array.
|
||||
* @returns True if the element is a newlines-between configuration object.
|
||||
*/
|
||||
export declare function isNewlinesBetweenOption(
|
||||
groupOption: GroupsOptions[number],
|
||||
): groupOption is GroupNewlinesBetweenOption
|
||||
Generated
Vendored
+37
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Type guard to check if a group option is a newlines-between configuration.
|
||||
*
|
||||
* Determines whether a group element contains a `newlinesBetween` property,
|
||||
* which indicates it's a special configuration object that controls spacing
|
||||
* between groups rather than being a regular group name or comment option.
|
||||
*
|
||||
* Newlines-between options are placed between group names in the configuration
|
||||
* to specify how many newlines should separate those groups in the sorted
|
||||
* output.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* const groups = [
|
||||
* 'imports',
|
||||
* { newlinesBetween: 1 }, // Add 1 newline between imports and types
|
||||
* 'types',
|
||||
* { newlinesBetween: 2 }, // Add 2 newlines between types and components
|
||||
* 'components',
|
||||
* { commentAbove: '// Utils' }, // Not a newlines option
|
||||
* 'utils',
|
||||
* ]
|
||||
*
|
||||
* isNewlinesBetweenOption(groups[0]) // false (string)
|
||||
* isNewlinesBetweenOption(groups[1]) // true (has newlinesBetween)
|
||||
* isNewlinesBetweenOption(groups[3]) // true (has newlinesBetween)
|
||||
* isNewlinesBetweenOption(groups[5]) // false (comment option)
|
||||
* ```
|
||||
*
|
||||
* @param groupOption - A single element from the groups configuration array.
|
||||
* @returns True if the element is a newlines-between configuration object.
|
||||
*/
|
||||
function isNewlinesBetweenOption(groupOption) {
|
||||
return typeof groupOption === 'object' && 'newlinesBetween' in groupOption
|
||||
}
|
||||
export { isNewlinesBetweenOption }
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user