eslint-plugin-perfectionist
Version:
ESLint plugin for sorting various data such as objects, imports, types, enums, JSX props, etc.
131 lines (130 loc) • 3.76 kB
JavaScript
import { getGroupIndex } from './get-group-index.js'
import { sortNodes } from './sort-nodes.js'
/**
* Sorts nodes by distributing them into groups and sorting each group
* independently.
*
* This is the core sorting function used by all Perfectionist rules. It
* implements a two-phase sorting strategy:
*
* 1. Distribute nodes into their respective groups based on group configuration
* 2. Sort each group independently using group-specific options.
*
* Ignored nodes (ESLint-disabled or manually ignored) maintain their original
* positions to preserve intentional code organization.
*
* @example
*
* ```ts
* // React component with grouped imports
* const nodes = [
* { name: './Button', group: 'internal' },
* { name: 'react', group: 'external' },
* { name: '@mui/material', group: 'external' },
* { name: './utils', group: 'internal' },
* ]
*
* sortNodesByGroups({
* groups: ['external', 'internal'],
* nodes,
* optionsByGroupIndexComputer: index => ({
* options: { type: 'alphabetical', order: 'asc' },
* }),
* })
* // Returns: ['@mui/material', 'react', './Button', './utils']
* // External group sorted first, then internal group
* ```
*
* @example
*
* ```ts
* // Class members with different sorting for each group
* class UserService {
* // Static members group - sorted alphabetically
* static VERSION = '1.0.0';
* static API_URL = 'https://api.example.com';
*
* // Properties group - sorted by line length
* id: string;
* cache: Map<string, User>;
*
* // Methods group - sorted naturally
* async getUser() { ... }
* async updateUser() { ... }
* }
* ```
*
* @example
*
* ```ts
* // Object with ignored properties
* const config = {
* apiUrl: 'https://api.example.com',
* timeout: 5000,
* // eslint-disable-next-line
* DEBUG_MODE: true, // This stays in place due to ESLint disable
* retries: 3,
* }
* // DEBUG_MODE maintains its position despite sorting
* ```
*
* @template T - Type of sorting node.
* @template Options - Type of sorting options.
* @param params - Parameters for group-based sorting.
* @returns Array of nodes sorted within their groups.
*/
function sortNodesByGroups({
comparatorByOptionsComputer,
optionsByGroupIndexComputer,
ignoreEslintDisabledNodes,
isNodeIgnoredForGroup,
isNodeIgnored,
groups,
nodes,
}) {
let nodesByNonIgnoredGroupIndex = {}
let ignoredNodeIndices = []
for (let [index, sortingNode] of nodes.entries()) {
if (
(sortingNode.isEslintDisabled && ignoreEslintDisabledNodes) ||
isNodeIgnored?.(sortingNode)
) {
ignoredNodeIndices.push(index)
continue
}
let groupIndex = getGroupIndex(groups, sortingNode)
nodesByNonIgnoredGroupIndex[groupIndex] ??= []
nodesByNonIgnoredGroupIndex[groupIndex].push(sortingNode)
}
let sortedNodes = []
for (let groupIndexString of Object.keys(
nodesByNonIgnoredGroupIndex,
).toSorted((a, b) => Number(a) - Number(b))) {
let groupIndex = Number(groupIndexString)
let options = optionsByGroupIndexComputer(groupIndex)
let nodesToPush = nodesByNonIgnoredGroupIndex[groupIndex]
let groupIgnoredNodes = new Set(
nodesToPush.filter(node =>
isNodeIgnoredForGroup?.({
groupOptions: options,
groupIndex,
node,
}),
),
)
sortedNodes.push(
...sortNodes({
isNodeIgnored: node => groupIgnoredNodes.has(node),
ignoreEslintDisabledNodes: false,
comparatorByOptionsComputer,
nodes: nodesToPush,
options,
}),
)
}
for (let ignoredIndex of ignoredNodeIndices) {
sortedNodes.splice(ignoredIndex, 0, nodes[ignoredIndex])
}
return sortedNodes
}
export { sortNodesByGroups }