eslint-plugin-perfectionist
Version:
ESLint plugin for sorting various data such as objects, imports, types, enums, JSX props, etc.
122 lines (121 loc) • 3.75 kB
JavaScript
import { computeNodesInCircularDependencies } from './compute-nodes-in-circular-dependencies.js'
import { isNodeDependentOnOtherNode } from './is-node-dependent-on-other-node.js'
/**
* Returns nodes topologically sorted by their dependencies.
*
* @param nodes - The nodes to sort.
* @param [extraOptions] - Additional sorting options.
* @returns The nodes sorted in topological order.
*/
/**
* Sorts nodes using topological sorting based on their dependencies.
*
* Implements a depth-first search algorithm to ensure that dependencies appear
* before the nodes that depend on them. This is crucial for maintaining logical
* order in code where some elements reference others.
*
* Nodes involved in circular dependencies are excluded from dependency-based
* ordering and retain their original relative positions.
*
* @example
*
* ```ts
* // TypeScript interfaces with inheritance
* const nodes = [
* {
* name: 'AdminUser',
* dependencies: ['AdminUser'],
* dependencyNames: ['User'],
* },
* { name: 'User', dependencies: ['User'], dependencyNames: [] },
* {
* name: 'GuestUser',
* dependencies: ['GuestUser'],
* dependencyNames: ['User'],
* },
* ]
* sortNodesByDependencies(nodes)
* // Returns: [User, AdminUser, GuestUser]
* // User must come first as others depend on it
* ```
*
* @example
*
* ```ts
* // React components with hook dependencies
* const nodes = [
* {
* name: 'MyComponent',
* dependencies: ['MyComponent'],
* dependencyNames: ['useAuth', 'useData'],
* },
* {
* name: 'useData',
* dependencies: ['useData'],
* dependencyNames: ['useApi'],
* },
* { name: 'useApi', dependencies: ['useApi'], dependencyNames: [] },
* { name: 'useAuth', dependencies: ['useAuth'], dependencyNames: [] },
* ]
* sortNodesByDependencies(nodes)
* // Returns: [useApi, useData, useAuth, MyComponent]
* // Hooks are ordered by their dependency chain
* ```
*
* @example
*
* ```ts
* // Object properties with computed values
* const config = {
* baseUrl: 'https://api.example.com',
* apiUrl: `${this.baseUrl}/v1`, // Depends on baseUrl
* authUrl: `${this.apiUrl}/auth`, // Depends on apiUrl
* headers: {
* 'API-Key': this.apiKey, // Depends on apiKey
* },
* apiKey: process.env.API_KEY,
* }
* // After sorting: baseUrl, apiKey, apiUrl, authUrl, headers
* ```
*
* @template T - Type of sorting node with dependencies.
* @param nodes - Array of nodes to sort by dependencies.
* @param extraOptions - Additional sorting options.
* @returns Topologically sorted array of nodes.
*/
function sortNodesByDependencies(nodes, extraOptions) {
let nodesInCircularDependencies = computeNodesInCircularDependencies(nodes)
let result = []
let visitedNodes = /* @__PURE__ */ new Set()
/**
* Recursively visits nodes in dependency order (depth-first).
*
* Ensures all dependencies of a node are visited and added to the result
* before the node itself. This creates the correct topological ordering.
*
* @param sortingNode - The node to visit.
*/
function visitNode(sortingNode) {
if (visitedNodes.has(sortingNode)) {
return
}
let dependentNodes = nodes
.filter(node => !nodesInCircularDependencies.has(node))
.filter(node => isNodeDependentOnOtherNode(node, sortingNode))
for (let dependentNode of dependentNodes) {
if (
!extraOptions.ignoreEslintDisabledNodes ||
!dependentNode.isEslintDisabled
) {
visitNode(dependentNode)
}
}
visitedNodes.add(sortingNode)
result.push(sortingNode)
}
for (let node of nodes) {
visitNode(node)
}
return result
}
export { sortNodesByDependencies }