eslint-plugin-perfectionist
Version:
ESLint plugin for sorting various data such as objects, imports, types, enums, JSX props, etc.
347 lines (346 loc) • 12.3 kB
JavaScript
import {
buildCommonJsonSchemas,
useExperimentalDependencyDetectionJsonSchema,
} from '../utils/json-schemas/common-json-schemas.js'
import {
buildCommonGroupsJsonSchemas,
newlinesBetweenJsonSchema,
} from '../utils/json-schemas/common-groups-json-schemas.js'
import { getGroupIndex } from '../utils/get-group-index.js'
import {
DEPENDENCY_ORDER_ERROR,
EXTRA_SPACING_ERROR,
GROUP_ORDER_ERROR,
MISSED_SPACING_ERROR,
ORDER_ERROR,
} from '../utils/report-errors.js'
import {
partitionByCommentJsonSchema,
partitionByNewLineJsonSchema,
} from '../utils/json-schemas/common-partition-json-schemas.js'
import { populateSortingNodeGroupsWithDependencies } from '../utils/populate-sorting-node-groups-with-dependencies.js'
import { validateNewlinesAndPartitionConfiguration } from '../utils/validate-newlines-and-partition-configuration.js'
import { buildOptionsByGroupIndexComputer } from '../utils/build-options-by-group-index-computer.js'
import { validateCustomSortConfiguration } from '../utils/validate-custom-sort-configuration.js'
import { validateGroupsConfiguration } from '../utils/validate-groups-configuration.js'
import { generatePredefinedGroups } from '../utils/generate-predefined-groups.js'
import { sortNodesByDependencies } from '../utils/sort-nodes-by-dependencies.js'
import { getEslintDisabledLines } from '../utils/get-eslint-disabled-lines.js'
import { doesCustomGroupMatch } from '../utils/does-custom-group-match.js'
import { isNodeEslintDisabled } from '../utils/is-node-eslint-disabled.js'
import { sortNodesByGroups } from '../utils/sort-nodes-by-groups.js'
import { reportAllErrors } from '../utils/report-all-errors.js'
import { shouldPartition } from '../utils/should-partition.js'
import { computeGroup } from '../utils/compute-group.js'
import { rangeToDiff } from '../utils/range-to-diff.js'
import { getSettings } from '../utils/get-settings.js'
import { complete } from '../utils/complete.js'
import { createEslintRule } from '../utils/create-eslint-rule.js'
import { buildOverloadSignatureNewlinesBetweenValueGetter } from '../utils/overload-signature/build-overload-signature-newlines-between-value-getter.js'
import { populateSortingNodeGroupsWithOverloadSignature } from '../utils/overload-signature/populate-sorting-node-groups-with-overload-signature.js'
import '../utils/assert-is-never.js'
import {
USAGE_TYPE_OPTION,
additionalCustomGroupMatchOptionsJsonSchema,
allModifiers,
allSelectors,
} from './sort-modules/types.js'
import { computeDependenciesBySortingNode } from './sort-modules/compute-dependencies-by-sorting-node.js'
import { buildComparatorByOptionsComputer } from './sort-modules/build-comparator-by-options-computer.js'
import { computeOverloadSignatureGroups } from './sort-modules/compute-overload-signature-groups.js'
import { computeNodeDetails } from './sort-modules/compute-node-details.js'
import { AST_NODE_TYPES } from '@typescript-eslint/utils'
/**
* Cache computed groups by modifiers and selectors for performance.
*/
var cachedGroupsByModifiersAndSelectors = /* @__PURE__ */ new Map()
var ORDER_ERROR_ID = 'unexpectedModulesOrder'
var GROUP_ORDER_ERROR_ID = 'unexpectedModulesGroupOrder'
var EXTRA_SPACING_ERROR_ID = 'extraSpacingBetweenModulesMembers'
var MISSED_SPACING_ERROR_ID = 'missedSpacingBetweenModulesMembers'
var DEPENDENCY_ORDER_ERROR_ID = 'unexpectedModulesDependencyOrder'
var defaultOptions = {
groups: [
'declare-enum',
'export-enum',
'enum',
['declare-interface', 'declare-type'],
['export-interface', 'export-type'],
['interface', 'type'],
'declare-class',
'class',
'export-class',
'declare-function',
'export-function',
'function',
],
useExperimentalDependencyDetection: true,
newlinesBetweenOverloadSignatures: 0,
fallbackSort: { type: 'unsorted' },
newlinesInside: 'newlinesBetween',
partitionByComment: false,
partitionByNewLine: false,
newlinesBetween: 'ignore',
specialCharacters: 'keep',
type: 'alphabetical',
ignoreCase: true,
customGroups: [],
locales: 'en-US',
alphabet: '',
order: 'asc',
}
var sort_modules_default = createEslintRule({
meta: {
schema: [
{
properties: {
...buildCommonJsonSchemas({
allowedAdditionalTypeValues: [USAGE_TYPE_OPTION],
}),
...buildCommonGroupsJsonSchemas({
additionalCustomGroupMatchProperties:
additionalCustomGroupMatchOptionsJsonSchema,
allowedAdditionalTypeValues: [USAGE_TYPE_OPTION],
}),
useExperimentalDependencyDetection:
useExperimentalDependencyDetectionJsonSchema,
newlinesBetweenOverloadSignatures: newlinesBetweenJsonSchema,
partitionByComment: partitionByCommentJsonSchema,
partitionByNewLine: partitionByNewLineJsonSchema,
},
additionalProperties: false,
type: 'object',
},
],
messages: {
[DEPENDENCY_ORDER_ERROR_ID]: DEPENDENCY_ORDER_ERROR,
[MISSED_SPACING_ERROR_ID]: MISSED_SPACING_ERROR,
[EXTRA_SPACING_ERROR_ID]: EXTRA_SPACING_ERROR,
[GROUP_ORDER_ERROR_ID]: GROUP_ORDER_ERROR,
[ORDER_ERROR_ID]: ORDER_ERROR,
},
docs: {
url: 'https://perfectionist.dev/rules/sort-modules',
description: 'Enforce sorted modules.',
recommended: true,
},
type: 'suggestion',
fixable: 'code',
},
create: context => {
let settings = getSettings(context.settings)
let options = complete(context.options.at(0), settings, defaultOptions)
validateCustomSortConfiguration(options)
validateGroupsConfiguration({
modifiers: allModifiers,
selectors: allSelectors,
options,
})
validateNewlinesAndPartitionConfiguration(options)
let { sourceCode, id } = context
let eslintDisabledLines = getEslintDisabledLines({
ruleName: id,
sourceCode,
})
return {
Program: program =>
analyzeModule({
eslintDisabledLines,
module: program,
sourceCode,
options,
context,
}),
}
},
defaultOptions: [defaultOptions],
name: 'sort-modules',
})
function analyzeModule({
eslintDisabledLines,
sourceCode,
options,
context,
module,
}) {
let optionsByGroupIndexComputer = buildOptionsByGroupIndexComputer(options)
let overloadSignatureNewlinesBetweenValueGetter =
buildOverloadSignatureNewlinesBetweenValueGetter(
options.newlinesBetweenOverloadSignatures,
)
let sortingNodeGroupsWithoutOverloadSignature = [[]]
for (let node of module.body) {
switch (node.type) {
case AST_NODE_TYPES.TSNamespaceExportDeclaration:
case AST_NODE_TYPES.ExportAllDeclaration:
case AST_NODE_TYPES.ImportDeclaration:
case AST_NODE_TYPES.DebuggerStatement:
case AST_NODE_TYPES.ContinueStatement:
case AST_NODE_TYPES.ReturnStatement:
case AST_NODE_TYPES.EmptyStatement:
case AST_NODE_TYPES.BreakStatement:
case AST_NODE_TYPES.WithStatement:
continue
case AST_NODE_TYPES.ExportDefaultDeclaration:
case AST_NODE_TYPES.ExportNamedDeclaration:
case AST_NODE_TYPES.TSInterfaceDeclaration:
case AST_NODE_TYPES.TSTypeAliasDeclaration:
case AST_NODE_TYPES.FunctionDeclaration:
case AST_NODE_TYPES.TSModuleDeclaration:
break
case AST_NODE_TYPES.TSImportEqualsDeclaration:
case AST_NODE_TYPES.VariableDeclaration:
case AST_NODE_TYPES.ExpressionStatement:
case AST_NODE_TYPES.TSExportAssignment:
case AST_NODE_TYPES.DoWhileStatement:
case AST_NODE_TYPES.LabeledStatement:
case AST_NODE_TYPES.SwitchStatement:
case AST_NODE_TYPES.WhileStatement:
case AST_NODE_TYPES.ForInStatement:
case AST_NODE_TYPES.ForOfStatement:
case AST_NODE_TYPES.ThrowStatement:
case AST_NODE_TYPES.BlockStatement:
case AST_NODE_TYPES.ForStatement:
case AST_NODE_TYPES.TryStatement:
case AST_NODE_TYPES.IfStatement:
sortingNodeGroupsWithoutOverloadSignature.push([])
continue
case AST_NODE_TYPES.TSDeclareFunction:
case AST_NODE_TYPES.TSEnumDeclaration:
case AST_NODE_TYPES.ClassDeclaration:
break
/* v8 ignore next 2 -- @preserve Exhaustive guard. */
default:
continue
}
let details = computeNodeDetails({
useExperimentalDependencyDetection:
options.useExperimentalDependencyDetection,
sourceCode,
node,
})
if (!details.nodeDetails) {
if (details.shouldPartitionAfterNode) {
sortingNodeGroupsWithoutOverloadSignature.push([])
}
if (details.moduleBlock) {
analyzeModule({
module: details.moduleBlock,
eslintDisabledLines,
sourceCode,
options,
context,
})
}
continue
}
let {
addSafetySemicolonWhenInline,
dependencyDetection,
dependencies,
decorators,
modifiers,
selector,
name,
} = details.nodeDetails
let group = computeGroup({
customGroupMatcher: customGroup =>
doesCustomGroupMatch({
selectors: [selector],
elementName: name,
customGroup,
decorators,
modifiers,
}),
predefinedGroups: generatePredefinedGroups({
cache: cachedGroupsByModifiersAndSelectors,
selectors: [selector],
modifiers,
}),
options,
})
let sortingNode = {
isEslintDisabled: isNodeEslintDisabled(node, eslintDisabledLines),
size: rangeToDiff(node, sourceCode),
addSafetySemicolonWhenInline,
dependencyNames: [name],
dependencyDetection,
dependencies,
group,
name,
node,
}
let lastSortingNode = sortingNodeGroupsWithoutOverloadSignature
.at(-1)
?.at(-1)
if (
shouldPartition({
lastSortingNode,
sortingNode,
sourceCode,
options,
})
) {
sortingNodeGroupsWithoutOverloadSignature.push([])
}
sortingNodeGroupsWithoutOverloadSignature.at(-1)?.push({
...sortingNode,
partitionId: sortingNodeGroupsWithoutOverloadSignature.length,
})
}
let sortingNodeGroups = populateSortingNodeGroupsWithOverloadSignature({
sortingNodeGroups: sortingNodeGroupsWithoutOverloadSignature,
overloadSignatureGroups: computeOverloadSignatureGroups(
sortingNodeGroupsWithoutOverloadSignature.flat().map(({ node }) => node),
),
})
if (options.useExperimentalDependencyDetection) {
sortingNodeGroups = populateSortingNodeGroupsWithDependencies({
dependenciesBySortingNode: computeDependenciesBySortingNode({
sortingNodes: sortingNodeGroups.flat(),
dependencyDetection: 'hard',
sourceCode,
}),
sortingNodeGroups,
})
}
let sortingNodes = sortingNodeGroups.flat()
reportAllErrors({
availableMessageIds: {
missedSpacingBetweenMembers: MISSED_SPACING_ERROR_ID,
unexpectedDependencyOrder: DEPENDENCY_ORDER_ERROR_ID,
extraSpacingBetweenMembers: EXTRA_SPACING_ERROR_ID,
unexpectedGroupOrder: GROUP_ORDER_ERROR_ID,
unexpectedOrder: ORDER_ERROR_ID,
},
newlinesBetweenValueGetter: overloadSignatureNewlinesBetweenValueGetter,
sortNodesExcludingEslintDisabled,
nodes: sortingNodes,
options,
context,
})
function sortNodesExcludingEslintDisabled(ignoreEslintDisabledNodes) {
return sortNodesByDependencies(
sortingNodeGroups.flatMap(sortingNodeGroup =>
sortNodesByGroups({
comparatorByOptionsComputer: buildComparatorByOptionsComputer({
useExperimentalDependencyDetection:
options.useExperimentalDependencyDetection,
sortingNodes: sortingNodeGroup,
ignoreEslintDisabledNodes,
sourceCode,
}),
isNodeIgnored: sortingNode =>
getGroupIndex(options.groups, sortingNode) ===
options.groups.length,
optionsByGroupIndexComputer,
ignoreEslintDisabledNodes,
nodes: sortingNodeGroup,
groups: options.groups,
}),
),
{ ignoreEslintDisabledNodes },
)
}
}
export { sort_modules_default as default }