186 lines
5.9 KiB
JavaScript
186 lines
5.9 KiB
JavaScript
import { getNewlinesBetweenOption } from './get-newlines-between-option.js'
|
|
import { getLinesBetween } from './get-lines-between.js'
|
|
import { getGroupIndex } from './get-group-index.js'
|
|
import { getNodeRange } from './get-node-range.js'
|
|
/**
|
|
* Generates fixes for adjusting newlines between groups of sorted elements.
|
|
*
|
|
* Processes pairs of adjacent nodes to ensure the correct number of newlines
|
|
* between them based on their group membership and configuration. The
|
|
* function:
|
|
*
|
|
* - Skips nodes in different partitions (they're sorted independently)
|
|
* - Skips cases where group order would be violated
|
|
* - Respects 'ignore' settings that preserve existing spacing
|
|
* - Calculates required newlines based on group configuration
|
|
* - Applies custom newlines getter if provided.
|
|
*
|
|
* @example
|
|
*
|
|
* ```ts
|
|
* // Configuration with newlines between groups
|
|
* const options = {
|
|
* groups: ['imports', 'types', 'functions'],
|
|
* newlinesBetween: 1, // 1 newline between groups
|
|
* }
|
|
*
|
|
* // Original: imports and types with no separation
|
|
* // After fix: adds 1 blank line between import and type groups
|
|
* ```
|
|
*
|
|
* @example
|
|
*
|
|
* ```ts
|
|
* // Custom newlines getter
|
|
* const newlinesBetweenValueGetter = ({
|
|
* left,
|
|
* right,
|
|
* computedNewlinesBetween,
|
|
* }) => {
|
|
* // Add extra newline before main function
|
|
* if (right.name === 'main') {
|
|
* return computedNewlinesBetween + 1
|
|
* }
|
|
* return computedNewlinesBetween
|
|
* }
|
|
* ```
|
|
*
|
|
* @template T - Type of sorting node.
|
|
* @param params - Parameters for generating newlines fixes.
|
|
* @returns Array of ESLint fix operations to adjust spacing.
|
|
*/
|
|
function makeNewlinesBetweenFixes({
|
|
newlinesBetweenValueGetter,
|
|
sortedNodes,
|
|
sourceCode,
|
|
options,
|
|
fixer,
|
|
nodes,
|
|
}) {
|
|
let fixes = []
|
|
for (let i = 0; i < sortedNodes.length - 1; i++) {
|
|
let sortingNode = nodes.at(i)
|
|
let nextSortingNode = nodes.at(i + 1)
|
|
let sortedSortingNode = sortedNodes.at(i)
|
|
let nextSortedSortingNode = sortedNodes.at(i + 1)
|
|
if (sortedSortingNode.partitionId !== nextSortedSortingNode.partitionId) {
|
|
continue
|
|
}
|
|
let nodeGroupIndex = getGroupIndex(options.groups, sortedSortingNode)
|
|
let nextNodeGroupIndex = getGroupIndex(
|
|
options.groups,
|
|
nextSortedSortingNode,
|
|
)
|
|
if (nodeGroupIndex > nextNodeGroupIndex) {
|
|
continue
|
|
}
|
|
let newlinesBetween = getNewlinesBetweenOption({
|
|
nextNodeGroupIndex,
|
|
nodeGroupIndex,
|
|
options,
|
|
})
|
|
newlinesBetween =
|
|
newlinesBetweenValueGetter?.({
|
|
computedNewlinesBetween: newlinesBetween,
|
|
right: nextSortedSortingNode,
|
|
left: sortedSortingNode,
|
|
}) ?? newlinesBetween
|
|
if (newlinesBetween === 'ignore') {
|
|
continue
|
|
}
|
|
let currentNodeRange = getNodeRange({
|
|
node: sortingNode.node,
|
|
sourceCode,
|
|
})
|
|
let nextNodeRangeStart = getNodeRange({
|
|
node: nextSortingNode.node,
|
|
sourceCode,
|
|
}).at(0)
|
|
if (
|
|
getLinesBetween(sourceCode, sortingNode, nextSortingNode) ===
|
|
newlinesBetween
|
|
) {
|
|
continue
|
|
}
|
|
let rangeToReplace = [currentNodeRange.at(1), nextNodeRangeStart]
|
|
let textBetweenNodes = sourceCode.text.slice(
|
|
currentNodeRange.at(1),
|
|
nextNodeRangeStart,
|
|
)
|
|
let rangeReplacement = computeRangeReplacement({
|
|
isOnSameLine:
|
|
sortingNode.node.loc.end.line === nextSortingNode.node.loc.start.line,
|
|
textBetweenNodes,
|
|
newlinesBetween,
|
|
})
|
|
fixes.push(fixer.replaceTextRange(rangeToReplace, rangeReplacement))
|
|
}
|
|
return fixes
|
|
}
|
|
/**
|
|
* Computes the replacement text for adjusting newlines between nodes.
|
|
*
|
|
* Handles the logic of adding or removing newlines while preserving necessary
|
|
* content like comments and semicolons. Special handling for:
|
|
*
|
|
* - Removing excessive newlines when fewer are needed
|
|
* - Adding newlines when more are needed
|
|
* - Preserving inline placement when nodes are on the same line.
|
|
*
|
|
* @param params - Parameters for computing replacement.
|
|
* @param params.textBetweenNodes - Original text between the two nodes.
|
|
* @param params.newlinesBetween - Number of newlines required (0 or more).
|
|
* @param params.isOnSameLine - Whether nodes are currently on the same line.
|
|
* @returns Replacement text with correct newlines, or undefined if no change
|
|
* needed.
|
|
*/
|
|
function computeRangeReplacement({
|
|
textBetweenNodes,
|
|
newlinesBetween,
|
|
isOnSameLine,
|
|
}) {
|
|
let textBetweenNodesWithoutInvalidNewlines =
|
|
getStringWithoutInvalidNewlines(textBetweenNodes)
|
|
if (newlinesBetween === 0) {
|
|
return textBetweenNodesWithoutInvalidNewlines
|
|
}
|
|
let rangeReplacement = textBetweenNodesWithoutInvalidNewlines
|
|
for (let index = 0; index < newlinesBetween; index++) {
|
|
rangeReplacement = addNewlineBeforeFirstNewline(rangeReplacement)
|
|
}
|
|
if (!isOnSameLine) {
|
|
return rangeReplacement
|
|
}
|
|
return addNewlineBeforeFirstNewline(rangeReplacement)
|
|
}
|
|
/**
|
|
* Adds a newline before the first existing newline or at the end of string.
|
|
*
|
|
* Used to incrementally add newlines while preserving existing content. If no
|
|
* newline exists, appends one at the end. Otherwise, inserts before the first
|
|
* newline to maintain proper spacing.
|
|
*
|
|
* @param value - String to add a newline to.
|
|
* @returns String with an additional newline.
|
|
*/
|
|
function addNewlineBeforeFirstNewline(value) {
|
|
let firstNewlineIndex = value.indexOf('\n')
|
|
if (firstNewlineIndex === -1) {
|
|
return `${value}\n`
|
|
}
|
|
return `${value.slice(0, firstNewlineIndex)}\n${value.slice(firstNewlineIndex)}`
|
|
}
|
|
/**
|
|
* Removes excessive newlines from a string.
|
|
*
|
|
* Normalizes spacing by collapsing multiple consecutive newlines into single
|
|
* newlines and removing empty lines that contain only whitespace.
|
|
*
|
|
* @param value - String potentially containing excessive newlines.
|
|
* @returns String with normalized newlines.
|
|
*/
|
|
function getStringWithoutInvalidNewlines(value) {
|
|
return value.replaceAll(/\n\s*\n/gu, '\n').replaceAll(/\n+/gu, '\n')
|
|
}
|
|
export { makeNewlinesBetweenFixes }
|