routie dev init since i didn't adhere to any proper guidance up until now

This commit is contained in:
2026-04-29 22:27:29 -06:00
commit e1dabb71e2
15301 changed files with 3562618 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
export * from './utils/alphabet.js'
export {}
+43
View File
@@ -0,0 +1,43 @@
import { ESLint, Linter } from 'eslint'
interface PluginConfigs extends Record<
string,
Linter.LegacyConfig | Linter.Config[] | Linter.Config
> {
'recommended-alphabetical-legacy': Linter.LegacyConfig
'recommended-line-length-legacy': Linter.LegacyConfig
'recommended-natural-legacy': Linter.LegacyConfig
'recommended-custom-legacy': Linter.LegacyConfig
'recommended-alphabetical': Linter.Config
'recommended-line-length': Linter.Config
'recommended-natural': Linter.Config
'recommended-custom': Linter.Config
}
export declare let rules: ESLint.Plugin['rules']
export declare let configs: PluginConfigs
declare const _default: {
configs: PluginConfigs
} & ESLint.Plugin
export default _default
export type { Options as SortVariableDeclarationsOptions } from './rules/sort-variable-declarations/types.js'
export type { Options as SortIntersectionTypesOptions } from './rules/sort-intersection-types/types.js'
export type { Options as SortImportAttributesOptions } from './rules/sort-import-attributes/types.js'
export type { Options as SortExportAttributesOptions } from './rules/sort-export-attributes/types.js'
export type { Options as SortHeritageClausesOptions } from './rules/sort-heritage-clauses/types.js'
export type { Options as SortArrayIncludesOptions } from './rules/sort-array-includes/types.js'
export type { Options as SortNamedImportsOptions } from './rules/sort-named-imports/types.js'
export type { Options as SortNamedExportsOptions } from './rules/sort-named-exports/types.js'
export type { Options as SortObjectTypesOptions } from './rules/sort-object-types/types.js'
export type { Options as SortSwitchCaseOptions } from './rules/sort-switch-case/types.js'
export type { Options as SortUnionTypesOptions } from './rules/sort-union-types/types.js'
export type { Options as SortInterfacesOptions } from './rules/sort-interfaces/types.js'
export type { Options as SortDecoratorsOptions } from './rules/sort-decorators/types.js'
export type { Options as SortJsxPropsOptions } from './rules/sort-jsx-props/types.js'
export type { Options as SortClassesOptions } from './rules/sort-classes/types.js'
export type { Options as SortImportsOptions } from './rules/sort-imports/types.js'
export type { Options as SortExportsOptions } from './rules/sort-exports/types.js'
export type { Options as SortObjectsOptions } from './rules/sort-objects/types.js'
export type { Options as SortModulesOptions } from './rules/sort-modules/types.js'
export type { Options as SortArraysOptions } from './rules/sort-arrays/types.js'
export type { Options as SortEnumsOptions } from './rules/sort-enums/types.js'
export type { Options as SortMapsOptions } from './rules/sort-maps/types.js'
export type { Options as SortSetsOptions } from './rules/sort-sets/types.js'
+119
View File
@@ -0,0 +1,119 @@
import { name, version } from './package.json.js'
import sort_variable_declarations_default from './rules/sort-variable-declarations.js'
import sort_union_types_default from './rules/sort-union-types.js'
import sort_intersection_types_default from './rules/sort-intersection-types.js'
import sort_import_attributes_default from './rules/sort-import-attributes.js'
import sort_export_attributes_default from './rules/sort-export-attributes.js'
import sort_heritage_clauses_default from './rules/sort-heritage-clauses.js'
import sort_array_includes_default from './rules/sort-array-includes.js'
import sort_named_imports_default from './rules/sort-named-imports.js'
import sort_named_exports_default from './rules/sort-named-exports.js'
import sort_object_types_default from './rules/sort-object-types.js'
import sort_switch_case_default from './rules/sort-switch-case.js'
import sort_interfaces_default from './rules/sort-interfaces.js'
import sort_decorators_default from './rules/sort-decorators.js'
import sort_jsx_props_default from './rules/sort-jsx-props.js'
import sort_classes_default from './rules/sort-classes.js'
import sort_imports_default from './rules/sort-imports.js'
import sort_exports_default from './rules/sort-exports.js'
import sort_objects_default from './rules/sort-objects.js'
import sort_modules_default from './rules/sort-modules.js'
import sort_arrays_default from './rules/sort-arrays.js'
import sort_enums_default from './rules/sort-enums.js'
import sort_maps_default from './rules/sort-maps.js'
import sort_sets_default from './rules/sort-sets.js'
var pluginName = 'perfectionist'
var recommendedRules = {
'sort-variable-declarations': sort_variable_declarations_default,
'sort-intersection-types': sort_intersection_types_default,
'sort-import-attributes': sort_import_attributes_default,
'sort-export-attributes': sort_export_attributes_default,
'sort-heritage-clauses': sort_heritage_clauses_default,
'sort-array-includes': sort_array_includes_default,
'sort-named-imports': sort_named_imports_default,
'sort-named-exports': sort_named_exports_default,
'sort-object-types': sort_object_types_default,
'sort-union-types': sort_union_types_default,
'sort-switch-case': sort_switch_case_default,
'sort-decorators': sort_decorators_default,
'sort-interfaces': sort_interfaces_default,
'sort-jsx-props': sort_jsx_props_default,
'sort-modules': sort_modules_default,
'sort-classes': sort_classes_default,
'sort-imports': sort_imports_default,
'sort-exports': sort_exports_default,
'sort-objects': sort_objects_default,
'sort-enums': sort_enums_default,
'sort-sets': sort_sets_default,
'sort-maps': sort_maps_default,
}
var rules = {
...recommendedRules,
'sort-arrays': sort_arrays_default,
}
var plugin = {
meta: {
version,
name,
},
rules,
}
function getRules(options) {
return Object.fromEntries(
Object.keys(recommendedRules).map(ruleName => [
`${pluginName}/${ruleName}`,
['error', options],
]),
)
}
function createConfig(options) {
return {
plugins: { [pluginName]: plugin },
rules: getRules(options),
}
}
function createLegacyConfig(options) {
return {
rules: getRules(options),
plugins: [pluginName],
}
}
var configs = {
'recommended-alphabetical-legacy': createLegacyConfig({
type: 'alphabetical',
order: 'asc',
}),
'recommended-line-length-legacy': createLegacyConfig({
type: 'line-length',
order: 'desc',
}),
'recommended-natural-legacy': createLegacyConfig({
type: 'natural',
order: 'asc',
}),
'recommended-custom-legacy': createLegacyConfig({
type: 'custom',
order: 'asc',
}),
'recommended-alphabetical': createConfig({
type: 'alphabetical',
order: 'asc',
}),
'recommended-line-length': createConfig({
type: 'line-length',
order: 'desc',
}),
'recommended-natural': createConfig({
type: 'natural',
order: 'asc',
}),
'recommended-custom': createConfig({
type: 'custom',
order: 'asc',
}),
}
var eslint_plugin_perfectionist_default = {
...plugin,
configs,
}
export { configs, eslint_plugin_perfectionist_default as default, rules }
@@ -0,0 +1,3 @@
var name = 'eslint-plugin-perfectionist'
var version = '5.9.0'
export { name, version }
@@ -0,0 +1,25 @@
import { JSONSchema4 } from '@typescript-eslint/utils/json-schema'
import { Options } from './sort-array-includes/types.js'
declare const ORDER_ERROR_ID = 'unexpectedArrayIncludesOrder'
declare const GROUP_ORDER_ERROR_ID = 'unexpectedArrayIncludesGroupOrder'
declare const EXTRA_SPACING_ERROR_ID = 'extraSpacingBetweenArrayIncludesMembers'
declare const MISSED_SPACING_ERROR_ID =
'missedSpacingBetweenArrayIncludesMembers'
type MessageId =
| typeof MISSED_SPACING_ERROR_ID
| typeof EXTRA_SPACING_ERROR_ID
| typeof GROUP_ORDER_ERROR_ID
| typeof ORDER_ERROR_ID
export declare let defaultOptions: Required<Options[number]>
export declare let jsonSchema: JSONSchema4
declare const _default: import('@typescript-eslint/utils/ts-eslint').RuleModule<
MessageId,
import('...js').SortArraysOptions,
{
recommended?: boolean
},
import('@typescript-eslint/utils/ts-eslint').RuleListener
> & {
name: string
}
export default _default
@@ -0,0 +1,131 @@
import {
buildCommonJsonSchemas,
buildUseConfigurationIfJsonSchema,
matchesAstSelectorJsonSchema,
} from '../utils/json-schemas/common-json-schemas.js'
import { buildCommonGroupsJsonSchemas } from '../utils/json-schemas/common-groups-json-schemas.js'
import {
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 { buildAstListeners } from '../utils/build-ast-listeners.js'
import { createEslintRule } from '../utils/create-eslint-rule.js'
import { additionalCustomGroupMatchOptionsJsonSchema } from './sort-arrays/types.js'
import { sortArray } from './sort-arrays/sort-array.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 = 'unexpectedArrayIncludesOrder'
var GROUP_ORDER_ERROR_ID = 'unexpectedArrayIncludesGroupOrder'
var EXTRA_SPACING_ERROR_ID = 'extraSpacingBetweenArrayIncludesMembers'
var MISSED_SPACING_ERROR_ID = 'missedSpacingBetweenArrayIncludesMembers'
var defaultOptions = {
fallbackSort: { type: 'unsorted' },
newlinesInside: 'newlinesBetween',
specialCharacters: 'keep',
partitionByComment: false,
partitionByNewLine: false,
newlinesBetween: 'ignore',
useConfigurationIf: {},
type: 'alphabetical',
groups: ['literal'],
ignoreCase: true,
locales: 'en-US',
customGroups: [],
alphabet: '',
order: 'asc',
}
var jsonSchema = {
items: {
properties: {
...buildCommonJsonSchemas(),
...buildCommonGroupsJsonSchemas({
additionalCustomGroupMatchProperties:
additionalCustomGroupMatchOptionsJsonSchema,
}),
useConfigurationIf: buildUseConfigurationIfJsonSchema({
additionalProperties: {
matchesAstSelector: matchesAstSelectorJsonSchema,
},
}),
partitionByComment: partitionByCommentJsonSchema,
partitionByNewLine: partitionByNewLineJsonSchema,
},
additionalProperties: false,
type: 'object',
},
uniqueItems: true,
type: 'array',
}
var sort_array_includes_default = createEslintRule({
meta: {
messages: {
[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: {
description: 'Enforce sorted arrays before include method.',
url: 'https://perfectionist.dev/rules/sort-array-includes',
recommended: true,
},
schema: jsonSchema,
type: 'suggestion',
fixable: 'code',
},
create: context =>
buildAstListeners({
nodeTypes: [AST_NODE_TYPES.NewExpression, AST_NODE_TYPES.ArrayExpression],
sorter: sortPotentiallyValidArray,
context,
}),
defaultOptions: [defaultOptions],
name: 'sort-array-includes',
})
function sortPotentiallyValidArray({ matchedAstSelectors, context, node }) {
if (!isValidArray()) {
return
}
sortArray({
availableMessageIds: {
missedSpacingBetweenMembers: MISSED_SPACING_ERROR_ID,
extraSpacingBetweenMembers: EXTRA_SPACING_ERROR_ID,
unexpectedGroupOrder: GROUP_ORDER_ERROR_ID,
unexpectedOrder: ORDER_ERROR_ID,
},
mustHaveMatchedContextOptions: false,
cachedGroupsByModifiersAndSelectors,
matchedAstSelectors,
defaultOptions,
context,
node,
})
function isValidArray() {
if (node.parent.type !== AST_NODE_TYPES.MemberExpression) {
return false
}
if (node.parent.property.type !== AST_NODE_TYPES.Identifier) {
return false
}
if (node.parent.property.name !== 'includes') {
return false
}
if (node.parent.parent.type !== AST_NODE_TYPES.CallExpression) {
return false
}
if (node.parent.parent.callee !== node.parent) {
return false
}
return true
}
}
export { sort_array_includes_default as default, defaultOptions, jsonSchema }
@@ -0,0 +1,2 @@
import { Options as SortArraysOptions } from '../sort-arrays/types.js'
export type Options = SortArraysOptions
@@ -0,0 +1,21 @@
import { Options } from './sort-arrays/types.js'
declare const ORDER_ERROR_ID = 'unexpectedArraysOrder'
declare const GROUP_ORDER_ERROR_ID = 'unexpectedArraysGroupOrder'
declare const EXTRA_SPACING_ERROR_ID = 'extraSpacingBetweenArraysMembers'
declare const MISSED_SPACING_ERROR_ID = 'missedSpacingBetweenArraysMembers'
type MessageId =
| typeof MISSED_SPACING_ERROR_ID
| typeof EXTRA_SPACING_ERROR_ID
| typeof GROUP_ORDER_ERROR_ID
| typeof ORDER_ERROR_ID
declare const _default: import('@typescript-eslint/utils/ts-eslint').RuleModule<
MessageId,
Options,
{
recommended?: boolean
},
import('@typescript-eslint/utils/ts-eslint').RuleListener
> & {
name: string
}
export default _default
@@ -0,0 +1,111 @@
import {
buildCommonJsonSchemas,
buildUseConfigurationIfJsonSchema,
matchesAstSelectorJsonSchema,
} from '../utils/json-schemas/common-json-schemas.js'
import { buildCommonGroupsJsonSchemas } from '../utils/json-schemas/common-groups-json-schemas.js'
import {
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 { buildAstListeners } from '../utils/build-ast-listeners.js'
import { createEslintRule } from '../utils/create-eslint-rule.js'
import { additionalCustomGroupMatchOptionsJsonSchema } from './sort-arrays/types.js'
import { sortArray } from './sort-arrays/sort-array.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 = 'unexpectedArraysOrder'
var GROUP_ORDER_ERROR_ID = 'unexpectedArraysGroupOrder'
var EXTRA_SPACING_ERROR_ID = 'extraSpacingBetweenArraysMembers'
var MISSED_SPACING_ERROR_ID = 'missedSpacingBetweenArraysMembers'
var defaultOptions = {
fallbackSort: { type: 'unsorted' },
newlinesInside: 'newlinesBetween',
specialCharacters: 'keep',
partitionByComment: false,
partitionByNewLine: false,
newlinesBetween: 'ignore',
useConfigurationIf: {},
type: 'alphabetical',
groups: ['literal'],
ignoreCase: true,
locales: 'en-US',
customGroups: [],
alphabet: '',
order: 'asc',
}
var jsonSchema = {
items: {
properties: {
...buildCommonJsonSchemas(),
...buildCommonGroupsJsonSchemas({
additionalCustomGroupMatchProperties:
additionalCustomGroupMatchOptionsJsonSchema,
}),
useConfigurationIf: buildUseConfigurationIfJsonSchema({
additionalProperties: {
matchesAstSelector: matchesAstSelectorJsonSchema,
},
}),
partitionByComment: partitionByCommentJsonSchema,
partitionByNewLine: partitionByNewLineJsonSchema,
},
required: ['useConfigurationIf'],
additionalProperties: false,
type: 'object',
},
uniqueItems: true,
type: 'array',
}
var sort_arrays_default = createEslintRule({
meta: {
messages: {
[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-arrays',
description: 'Enforce sorted arrays.',
recommended: false,
},
schema: jsonSchema,
type: 'suggestion',
fixable: 'code',
},
create: context =>
buildAstListeners({
nodeTypes: [AST_NODE_TYPES.NewExpression, AST_NODE_TYPES.ArrayExpression],
context,
sorter,
}),
defaultOptions: [defaultOptions],
name: 'sort-arrays',
})
function sorter({ matchedAstSelectors, context, node }) {
sortArray({
availableMessageIds: {
missedSpacingBetweenMembers: MISSED_SPACING_ERROR_ID,
extraSpacingBetweenMembers: EXTRA_SPACING_ERROR_ID,
unexpectedGroupOrder: GROUP_ORDER_ERROR_ID,
unexpectedOrder: ORDER_ERROR_ID,
},
cachedGroupsByModifiersAndSelectors,
mustHaveMatchedContextOptions: true,
matchedAstSelectors,
defaultOptions,
context,
node,
})
}
export { sort_arrays_default as default }
@@ -0,0 +1,11 @@
import { TSESTree } from '@typescript-eslint/types'
/**
* Computes array elements for the given expression.
*
* @param expression - The expression to compute array elements from.
* @returns An array of elements if the expression is an array or a new
* expression, otherwise null.
*/
export declare function computeArrayElements(
expression: TSESTree.ArrayExpression | TSESTree.NewExpression,
): (TSESTree.SpreadElement | TSESTree.Expression | null)[] | null
@@ -0,0 +1,27 @@
import { UnreachableCaseError } from '../../utils/unreachable-case-error.js'
import { AST_NODE_TYPES } from '@typescript-eslint/utils'
/**
* Computes array elements for the given expression.
*
* @param expression - The expression to compute array elements from.
* @returns An array of elements if the expression is an array or a new
* expression, otherwise null.
*/
function computeArrayElements(expression) {
switch (expression.type) {
case AST_NODE_TYPES.ArrayExpression:
return expression.elements
case AST_NODE_TYPES.NewExpression:
if (expression.callee.type !== AST_NODE_TYPES.Identifier) {
return null
}
if (expression.callee.name !== 'Array') {
return null
}
return expression.arguments
/* v8 ignore next 2 -- @preserve Exhaustive guard. */
default:
throw new UnreachableCaseError(expression)
}
}
export { computeArrayElements }
@@ -0,0 +1,25 @@
import { RuleContext } from '@typescript-eslint/utils/ts-eslint'
import { TSESTree } from '@typescript-eslint/types'
import { Options } from './types.js'
/**
* Computes the matched context options for a given array node.
*
* @param params - Parameters.
* @param params.matchedAstSelectors - The matched AST selectors for an array
* node.
* @param params.elements - The array elements to compute the context options
* for.
* @param params.context - The rule context.
* @returns The matched context options or undefined if none match.
*/
export declare function computeMatchedContextOptions<
MessageIds extends string,
>({
matchedAstSelectors,
elements,
context,
}: {
elements: (TSESTree.SpreadElement | TSESTree.Expression | null)[]
context: Readonly<RuleContext<MessageIds, Options>>
matchedAstSelectors: ReadonlySet<string>
}): Options[number] | undefined
@@ -0,0 +1,51 @@
import { passesAllNamesMatchPatternFilter } from '../../utils/context-matching/passes-all-names-match-pattern-filter.js'
import { passesAstSelectorFilter } from '../../utils/context-matching/passes-ast-selector-filter.js'
import { computeNodeName } from './compute-node-name.js'
/**
* Computes the matched context options for a given array node.
*
* @param params - Parameters.
* @param params.matchedAstSelectors - The matched AST selectors for an array
* node.
* @param params.elements - The array elements to compute the context options
* for.
* @param params.context - The rule context.
* @returns The matched context options or undefined if none match.
*/
function computeMatchedContextOptions({
matchedAstSelectors,
elements,
context,
}) {
let nodeNames = elements
.filter(element => element !== null)
.map(element =>
computeNodeName({
sourceCode: context.sourceCode,
node: element,
}),
)
return context.options.find(options =>
isContextOptionMatching({
matchedAstSelectors,
nodeNames,
options,
}),
)
}
function isContextOptionMatching({ matchedAstSelectors, nodeNames, options }) {
if (!options.useConfigurationIf) {
return true
}
return (
passesAllNamesMatchPatternFilter({
allNamesMatchPattern: options.useConfigurationIf.allNamesMatchPattern,
nodeNames,
}) &&
passesAstSelectorFilter({
matchesAstSelector: options.useConfigurationIf.matchesAstSelector,
matchedAstSelectors,
})
)
}
export { computeMatchedContextOptions }
@@ -0,0 +1,17 @@
import { TSESTree } from '@typescript-eslint/types'
import { TSESLint } from '@typescript-eslint/utils'
/**
* Computes the name of an array member.
*
* @param props - The parameters object.
* @param props.sourceCode - ESLint source code object for text extraction.
* @param props.node - The AST node representing an array member.
* @returns The name of the array member.
*/
export declare function computeNodeName({
sourceCode,
node,
}: {
node: TSESTree.SpreadElement | TSESTree.Expression
sourceCode: TSESLint.SourceCode
}): string
@@ -0,0 +1,15 @@
import { AST_NODE_TYPES } from '@typescript-eslint/utils'
/**
* Computes the name of an array member.
*
* @param props - The parameters object.
* @param props.sourceCode - ESLint source code object for text extraction.
* @param props.node - The AST node representing an array member.
* @returns The name of the array member.
*/
function computeNodeName({ sourceCode, node }) {
return node.type === AST_NODE_TYPES.Literal ?
`${node.value}`
: sourceCode.getText(node)
}
export { computeNodeName }
@@ -0,0 +1,25 @@
import { RuleContext } from '@typescript-eslint/utils/ts-eslint'
import { TSESTree } from '@typescript-eslint/types'
import { Options } from './types.js'
export declare function sortArray<MessageIds extends string>({
cachedGroupsByModifiersAndSelectors,
mustHaveMatchedContextOptions,
availableMessageIds,
matchedAstSelectors,
defaultOptions,
context,
node,
}: {
availableMessageIds: {
missedSpacingBetweenMembers: MessageIds
extraSpacingBetweenMembers: MessageIds
unexpectedGroupOrder: MessageIds
unexpectedOrder: MessageIds
}
cachedGroupsByModifiersAndSelectors: Map<string, string[]>
node: TSESTree.ArrayExpression | TSESTree.NewExpression
context: Readonly<RuleContext<MessageIds, Options>>
defaultOptions: Required<Options[number]>
matchedAstSelectors: ReadonlySet<string>
mustHaveMatchedContextOptions: boolean
}): void
@@ -0,0 +1,136 @@
import { validateNewlinesAndPartitionConfiguration } from '../../utils/validate-newlines-and-partition-configuration.js'
import { defaultComparatorByOptionsComputer } from '../../utils/compare/default-comparator-by-options-computer.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 { 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 { isSortable } from '../../utils/is-sortable.js'
import { complete } from '../../utils/complete.js'
import { allSelectors } from './types.js'
import { computeNodeName } from './compute-node-name.js'
import { computeMatchedContextOptions } from './compute-matched-context-options.js'
import { computeArrayElements } from './compute-array-elements.js'
import { AST_NODE_TYPES } from '@typescript-eslint/utils'
function sortArray({
cachedGroupsByModifiersAndSelectors,
mustHaveMatchedContextOptions,
availableMessageIds,
matchedAstSelectors,
defaultOptions,
context,
node,
}) {
let elements = computeArrayElements(node)
if (!elements) {
return
}
if (!isSortable(elements)) {
return
}
let { sourceCode, id } = context
let settings = getSettings(context.settings)
let matchedContextOptions = computeMatchedContextOptions({
matchedAstSelectors,
elements,
context,
})
if (mustHaveMatchedContextOptions && !matchedContextOptions) {
return
}
let options = complete(matchedContextOptions, settings, defaultOptions)
validateCustomSortConfiguration(options)
validateGroupsConfiguration({
selectors: allSelectors,
modifiers: [],
options,
})
validateNewlinesAndPartitionConfiguration(options)
let eslintDisabledLines = getEslintDisabledLines({
ruleName: id,
sourceCode,
})
let optionsByGroupIndexComputer = buildOptionsByGroupIndexComputer(options)
let formattedMembers = elements.reduce(
(accumulator, element) => {
if (element === null) {
return accumulator
}
if (element.type === AST_NODE_TYPES.SpreadElement) {
accumulator.push([])
return accumulator
}
let name = computeNodeName({
node: element,
sourceCode,
})
let selector = 'literal'
let group = computeGroup({
customGroupMatcher: customGroup =>
doesCustomGroupMatch({
selectors: [selector],
elementName: name,
modifiers: [],
customGroup,
}),
predefinedGroups: generatePredefinedGroups({
cache: cachedGroupsByModifiersAndSelectors,
selectors: [selector],
modifiers: [],
}),
options,
})
let sortingNode = {
isEslintDisabled: isNodeEslintDisabled(element, eslintDisabledLines),
size: rangeToDiff(element, sourceCode),
node: element,
group,
name,
}
let lastSortingNode = accumulator.at(-1)?.at(-1)
if (
shouldPartition({
lastSortingNode,
sortingNode,
sourceCode,
options,
})
) {
accumulator.push([])
}
accumulator.at(-1).push({
...sortingNode,
partitionId: accumulator.length,
})
return accumulator
},
[[]],
)
function sortNodesExcludingEslintDisabled(ignoreEslintDisabledNodes) {
return formattedMembers.flatMap(nodes =>
sortNodesByGroups({
comparatorByOptionsComputer: defaultComparatorByOptionsComputer,
optionsByGroupIndexComputer,
ignoreEslintDisabledNodes,
groups: options.groups,
nodes,
}),
)
}
reportAllErrors({
sortNodesExcludingEslintDisabled,
availableMessageIds,
options,
context,
nodes: formattedMembers.flat(),
})
}
export { sortArray }
@@ -0,0 +1,74 @@
import { JSONSchema4 } from '@typescript-eslint/utils/json-schema'
import { RegexOption, TypeOption } from '../../types/common-options.js'
import { AllCommonOptions } from '../../types/all-common-options.js'
/**
* Configuration options for the sort-array-includes rule.
*
* This rule enforces the sorting of arrays passed to the `.includes()` method,
* ensuring consistent ordering of array elements for better readability and
* maintainability.
*/
export type Options = Partial<
{
/**
* Conditional configuration based on pattern matching.
*/
useConfigurationIf: {
/**
* Regular expression pattern to match against all array element names.
* The rule is only applied when all names match this pattern.
*/
allNamesMatchPattern?: RegexOption
/**
* AST selector to match against ArrayExpression nodes.
*/
matchesAstSelector?: string
}
} & AllCommonOptions<
TypeOption,
AdditionalSortOptions,
CustomGroupMatchOptions
>
>[]
/**
* Represents the type of array element selector. Note: Spread elements are not
* sorted and act as partition boundaries.
*/
export type Selector = (typeof allSelectors)[number]
/**
* Additional configuration for a single custom group.
*
* Custom groups allow fine-grained control over how array elements are grouped
* and sorted based on their names and types.
*
* @example
*
* ```ts
* {
* "selector": "literal"
* }
* ```
*/
interface CustomGroupMatchOptions {
/**
* Specifies the type of array elements to include in this group. Only
* 'literal' is available since spread elements create partition boundaries
* and are not sorted.
*/
selector?: Selector
}
type AdditionalSortOptions = object
/**
* Complete list of available selectors for array elements. Used for validation
* and JSON schema generation.
*/
export declare let allSelectors: readonly ['literal']
/**
* Additional custom group match options JSON schema. Used by ESLint to validate
* rule options at configuration time.
*/
export declare let additionalCustomGroupMatchOptionsJsonSchema: Record<
string,
JSONSchema4
>
export {}
@@ -0,0 +1,14 @@
import { buildCustomGroupSelectorJsonSchema } from '../../utils/json-schemas/common-groups-json-schemas.js'
/**
* Complete list of available selectors for array elements. Used for validation
* and JSON schema generation.
*/
var allSelectors = ['literal']
/**
* Additional custom group match options JSON schema. Used by ESLint to validate
* rule options at configuration time.
*/
var additionalCustomGroupMatchOptionsJsonSchema = {
selector: buildCustomGroupSelectorJsonSchema(allSelectors),
}
export { additionalCustomGroupMatchOptionsJsonSchema, allSelectors }
@@ -0,0 +1,12 @@
import { MessageId, Options } from './sort-classes/types.js'
declare const _default: import('@typescript-eslint/utils/ts-eslint').RuleModule<
MessageId,
Options,
{
recommended?: boolean
},
import('@typescript-eslint/utils/ts-eslint').RuleListener
> & {
name: string
}
export default _default
@@ -0,0 +1,87 @@
import {
buildCommonJsonSchemas,
buildRegexJsonSchema,
buildUseConfigurationIfJsonSchema,
matchesAstSelectorJsonSchema,
useExperimentalDependencyDetectionJsonSchema,
} from '../utils/json-schemas/common-json-schemas.js'
import {
buildCommonGroupsJsonSchemas,
newlinesBetweenJsonSchema,
} from '../utils/json-schemas/common-groups-json-schemas.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 { buildAstListeners } from '../utils/build-ast-listeners.js'
import { createEslintRule } from '../utils/create-eslint-rule.js'
import {
DEPENDENCY_ORDER_ERROR_ID,
EXTRA_SPACING_ERROR_ID,
GROUP_ORDER_ERROR_ID,
MISSED_SPACING_ERROR_ID,
ORDER_ERROR_ID,
additionalCustomGroupMatchOptionsJsonSchema,
} from './sort-classes/types.js'
import { defaultOptions, sortClass } from './sort-classes/sort-class.js'
import { AST_NODE_TYPES } from '@typescript-eslint/utils'
var sort_classes_default = createEslintRule({
meta: {
schema: {
items: {
properties: {
...buildCommonJsonSchemas(),
...buildCommonGroupsJsonSchemas({
additionalCustomGroupMatchProperties:
additionalCustomGroupMatchOptionsJsonSchema,
}),
useConfigurationIf: buildUseConfigurationIfJsonSchema({
additionalProperties: {
matchesAstSelector: matchesAstSelectorJsonSchema,
},
}),
useExperimentalDependencyDetection:
useExperimentalDependencyDetectionJsonSchema,
newlinesBetweenOverloadSignatures: newlinesBetweenJsonSchema,
ignoreCallbackDependenciesPatterns: buildRegexJsonSchema(),
partitionByComment: partitionByCommentJsonSchema,
partitionByNewLine: partitionByNewLineJsonSchema,
},
additionalProperties: false,
type: 'object',
},
uniqueItems: true,
type: 'array',
},
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-classes',
description: 'Enforce sorted classes.',
recommended: true,
},
type: 'suggestion',
fixable: 'code',
},
create: context =>
buildAstListeners({
nodeTypes: [AST_NODE_TYPES.ClassBody],
sorter: sortClass,
context,
}),
defaultOptions: [defaultOptions],
name: 'sort-classes',
})
export { sort_classes_default as default }
@@ -0,0 +1,20 @@
import { TSESTree } from '@typescript-eslint/types'
import { TSESLint } from '@typescript-eslint/utils'
import { SortClassesSortingNode } from './types.js'
import { RegexOption } from '../../types/common-options.js'
type SortingNodeWithoutDependencies = Omit<
SortClassesSortingNode,
'dependencies'
>
export declare function computeDependenciesBySortingNode({
ignoreCallbackDependenciesPatterns,
sortingNodes,
sourceCode,
classBody,
}: {
ignoreCallbackDependenciesPatterns: RegexOption
sortingNodes: SortingNodeWithoutDependencies[]
sourceCode: TSESLint.SourceCode
classBody: TSESTree.ClassBody
}): Map<SortingNodeWithoutDependencies, SortingNodeWithoutDependencies[]>
export {}
@@ -0,0 +1,186 @@
import { UnreachableCaseError } from '../../utils/unreachable-case-error.js'
import { matches } from '../../utils/matches.js'
import { computeDependenciesBySortingNode as computeDependenciesBySortingNode$1 } from '../../utils/compute-dependencies-by-sorting-node.js'
import { computeParentNodesWithTypes } from '../../utils/compute-parent-nodes-with-types.js'
import { computeIdentifierNameDetails } from './compute-identifier-name-details.js'
import { AST_NODE_TYPES, AST_TOKEN_TYPES } from '@typescript-eslint/utils'
function computeDependenciesBySortingNode({
ignoreCallbackDependenciesPatterns,
sortingNodes,
sourceCode,
classBody,
}) {
let dependenciesBySortingNode = computeDependenciesBySortingNode$1({
additionalIdentifierDependenciesComputer:
buildAdditionalIdentifierDependenciesComputer({
ignoreCallbackDependenciesPatterns,
staticSortingNodes: sortingNodes.filter(node => node.isStatic),
classBody,
}),
shouldIgnoreSortingNodeComputer: sortingNode =>
shouldIgnoreDependencyComputation(sortingNode.node),
sortingNodes,
sourceCode,
})
let thisDependenciesBySortingNode =
computeThisExpressionDependenciesBySortingNode({
ignoreCallbackDependenciesPatterns,
sortingNodes,
sourceCode,
})
for (let [sortingNode, dependencies] of thisDependenciesBySortingNode) {
let existingDependencies = dependenciesBySortingNode.get(sortingNode) ?? []
dependenciesBySortingNode.set(sortingNode, [
...existingDependencies,
...dependencies,
])
}
return dependenciesBySortingNode
}
function computeIdentifierOrThisExpressionDependency({
ignoreCallbackDependenciesPatterns,
sortingNodes,
classElement,
node,
}) {
if (shouldIgnoreCallbackDependency()) {
return null
}
let { parent } = node
/* v8 ignore if -- @preserve Unsure how we can reach that case */
if (parent.type !== AST_NODE_TYPES.MemberExpression) {
return null
}
let dependencyName = computeDependencyNameFromMemberExpression(parent)
/* v8 ignore if -- @preserve Unsure how we can reach that case */
if (!dependencyName) {
return null
}
return (
sortingNodes.find(
currentSortingNode => currentSortingNode.name === dependencyName.name,
) ?? null
)
function computeDependencyNameFromMemberExpression(memberExpression) {
switch (memberExpression.property.type) {
case AST_NODE_TYPES.PrivateIdentifier:
case AST_NODE_TYPES.Identifier:
case AST_NODE_TYPES.Literal:
return computeIdentifierNameDetails(memberExpression.property)
/* v8 ignore next 2 -- @preserve Unhandled cases */
default:
return null
}
}
function shouldIgnoreCallbackDependency() {
let [firstCallExpressionParent] = computeParentNodesWithTypes({
allowedTypes: [AST_NODE_TYPES.CallExpression],
maxParent: classElement,
consecutiveOnly: false,
node,
})
if (!firstCallExpressionParent) {
return false
}
if (!('name' in firstCallExpressionParent.callee)) {
return false
}
return matches(
firstCallExpressionParent.callee.name,
ignoreCallbackDependenciesPatterns,
)
}
}
function computeThisExpressionDependenciesBySortingNode({
ignoreCallbackDependenciesPatterns,
sortingNodes,
sourceCode,
}) {
let dependenciesBySortingNode = /* @__PURE__ */ new Map()
let staticSortingNodes = sortingNodes.filter(node => node.isStatic)
let nonStaticSortingNodes = sortingNodes.filter(node => !node.isStatic)
let relevantSortingNodes = sortingNodes.filter(
sortingNode => !shouldIgnoreDependencyComputation(sortingNode.node),
)
for (let sortingNode of relevantSortingNodes) {
let dependencies = computeThisExpressionsInsideClassElement({
classElement: sortingNode.node,
sourceCode,
})
.map(thisExpression =>
computeIdentifierOrThisExpressionDependency({
sortingNodes:
sortingNode.isStatic ? staticSortingNodes : nonStaticSortingNodes,
ignoreCallbackDependenciesPatterns,
classElement: sortingNode.node,
node: thisExpression,
}),
)
.filter(dependency => dependency !== null)
if (dependencies.length === 0) {
continue
}
dependenciesBySortingNode.set(sortingNode, dependencies)
}
return dependenciesBySortingNode
}
function buildAdditionalIdentifierDependenciesComputer({
ignoreCallbackDependenciesPatterns,
staticSortingNodes,
classBody,
}) {
return ({ referencingSortingNode, reference }) => {
if (!reference.resolved?.identifiers[0]) {
return []
}
let classIdentifier = classBody.parent.id
if (reference.resolved?.identifiers[0] !== classIdentifier) {
return []
}
let dependency = computeIdentifierOrThisExpressionDependency({
classElement: referencingSortingNode.node,
ignoreCallbackDependenciesPatterns,
sortingNodes: staticSortingNodes,
node: reference.identifier,
})
return dependency ? [dependency] : []
}
}
function shouldIgnoreDependencyComputation(node) {
switch (node.type) {
case AST_NODE_TYPES.TSAbstractPropertyDefinition:
case AST_NODE_TYPES.TSAbstractMethodDefinition:
case AST_NODE_TYPES.StaticBlock:
return false
case AST_NODE_TYPES.TSAbstractAccessorProperty:
case AST_NODE_TYPES.AccessorProperty:
case AST_NODE_TYPES.MethodDefinition:
case AST_NODE_TYPES.TSIndexSignature:
return true
case AST_NODE_TYPES.PropertyDefinition:
return (
node.value?.type === AST_NODE_TYPES.ArrowFunctionExpression ||
node.value?.type === AST_NODE_TYPES.FunctionExpression
)
/* v8 ignore next 2 -- @preserve Exhaustive guard. */
default:
throw new UnreachableCaseError(node)
}
}
function computeThisExpressionsInsideClassElement({
classElement,
sourceCode,
}) {
return sourceCode
.getTokens(classElement)
.filter(isThisToken)
.map(computeTokenNode)
.filter(node => node?.type === AST_NODE_TYPES.ThisExpression)
function computeTokenNode(token) {
return sourceCode.getNodeByRangeIndex(token.range[0])
}
function isThisToken(token) {
return token.type === AST_TOKEN_TYPES.Keyword && token.value === 'this'
}
}
export { computeDependenciesBySortingNode }
@@ -0,0 +1,29 @@
import { TSESTree } from '@typescript-eslint/types'
import { RegexOption } from '../../types/common-options.js'
/**
* Computes the dependencies of a class member AST node.
*
* @deprecated - To remove when experimental dependency detection is the only.
* @param params - Parameters object.
* @param params.ignoreCallbackDependenciesPatterns - Patterns to ignore
* callback dependencies.
* @param params.useExperimentalDependencyDetection - Whether to use
* experimental dependency detection.
* @param params.isMemberStatic - Indicates if the member is static.
* @param params.expression - The AST node expression to analyze.
* @param params.className - The name of the class, if available.
* @returns The names of the dependencies.
*/
export declare function computeDependencies({
ignoreCallbackDependenciesPatterns,
useExperimentalDependencyDetection,
isMemberStatic,
expression,
className,
}: {
expression: TSESTree.StaticBlock | TSESTree.Expression
ignoreCallbackDependenciesPatterns: RegexOption
useExperimentalDependencyDetection: boolean
className: undefined | string
isMemberStatic: boolean
}): string[]
@@ -0,0 +1,166 @@
import { matches } from '../../utils/matches.js'
import { computeIdentifierNameDetails } from './compute-identifier-name-details.js'
import { computeDependencyName } from './compute-dependency-name.js'
import { AST_NODE_TYPES } from '@typescript-eslint/utils'
/**
* Computes the dependencies of a class member AST node.
*
* @deprecated - To remove when experimental dependency detection is the only.
* @param params - Parameters object.
* @param params.ignoreCallbackDependenciesPatterns - Patterns to ignore
* callback dependencies.
* @param params.useExperimentalDependencyDetection - Whether to use
* experimental dependency detection.
* @param params.isMemberStatic - Indicates if the member is static.
* @param params.expression - The AST node expression to analyze.
* @param params.className - The name of the class, if available.
* @returns The names of the dependencies.
*/
function computeDependencies({
ignoreCallbackDependenciesPatterns,
useExperimentalDependencyDetection,
isMemberStatic,
expression,
className,
}) {
if (useExperimentalDependencyDetection) {
return []
}
let dependencies = []
traverseNode(expression)
return dependencies
function checkNode(nodeValue) {
switch (nodeValue.type) {
case AST_NODE_TYPES.ConditionalExpression:
traverseNode(nodeValue.test)
traverseNode(nodeValue.consequent)
traverseNode(nodeValue.alternate)
break
case AST_NODE_TYPES.MemberExpression:
dependencies.push(
...computeMemberExpressionDependencies({
memberExpression: nodeValue,
isMemberStatic,
className,
}),
)
break
case AST_NODE_TYPES.CallExpression:
if (!('name' in nodeValue.callee)) {
traverseNode(nodeValue.arguments)
break
}
if (
matches(nodeValue.callee.name, ignoreCallbackDependenciesPatterns)
) {
break
}
traverseNode(nodeValue.arguments)
break
case AST_NODE_TYPES.NewExpression:
traverseNode(nodeValue.arguments)
break
case AST_NODE_TYPES.Property:
traverseNode(nodeValue.key)
traverseNode(nodeValue.value)
break
default:
break
}
if ('argument' in nodeValue && nodeValue.argument) {
traverseNode(nodeValue.argument)
}
if ('body' in nodeValue && nodeValue.body) {
traverseNode(nodeValue.body)
}
if ('callee' in nodeValue) {
traverseNode(nodeValue.callee)
}
if ('declarations' in nodeValue) {
traverseNode(nodeValue.declarations)
}
if ('elements' in nodeValue) {
traverseNode(
nodeValue.elements.filter(currentNode => currentNode !== null),
)
}
if (
'expression' in nodeValue &&
typeof nodeValue.expression !== 'boolean'
) {
traverseNode(nodeValue.expression)
}
if ('expressions' in nodeValue) {
traverseNode(nodeValue.expressions)
}
if ('init' in nodeValue && nodeValue.init) {
traverseNode(nodeValue.init)
}
if ('left' in nodeValue) {
traverseNode(nodeValue.left)
}
if ('object' in nodeValue) {
traverseNode(nodeValue.object)
}
if ('properties' in nodeValue) {
traverseNode(nodeValue.properties)
}
if ('right' in nodeValue) {
traverseNode(nodeValue.right)
}
}
function traverseNode(nodeValue) {
if (Array.isArray(nodeValue)) {
for (let nodeItem of nodeValue) {
traverseNode(nodeItem)
}
} else {
checkNode(nodeValue)
}
}
}
function computeMemberExpressionDependencies({
memberExpression,
isMemberStatic,
className,
}) {
switch (memberExpression.object.type) {
case AST_NODE_TYPES.ThisExpression:
return computeIdentifierOrThisExpressionDependencies()
case AST_NODE_TYPES.Identifier:
return memberExpression.object.name === className ?
computeIdentifierOrThisExpressionDependencies()
: []
default:
return []
}
function computeIdentifierOrThisExpressionDependencies() {
let dependency = computeDependencyFromProperty()
/* v8 ignore next 2 -- @preserve Unhandled cases */
if (!dependency) {
return []
}
return [dependency]
}
function computeDependencyFromProperty() {
switch (memberExpression.property.type) {
case AST_NODE_TYPES.PrivateIdentifier:
case AST_NODE_TYPES.Identifier:
case AST_NODE_TYPES.Literal: {
let { nameWithoutStartingHash, hasPrivateHash } =
computeIdentifierNameDetails(memberExpression.property)
return computeDependencyName({
isStatic:
isMemberStatic ||
memberExpression.object.type === AST_NODE_TYPES.Identifier,
nodeNameWithoutStartingHash: nameWithoutStartingHash,
hasPrivateHash,
})
}
/* v8 ignore next 2 -- @preserve Unhandled cases */
default:
return null
}
}
}
export { computeDependencies }
@@ -0,0 +1,9 @@
export declare function computeDependencyName({
nodeNameWithoutStartingHash,
hasPrivateHash,
isStatic,
}: {
nodeNameWithoutStartingHash: string
hasPrivateHash: boolean
isStatic: boolean
}): string
@@ -0,0 +1,8 @@
function computeDependencyName({
nodeNameWithoutStartingHash,
hasPrivateHash,
isStatic,
}) {
return `${isStatic ? 'static ' : ''}${hasPrivateHash ? '#' : ''}${nodeNameWithoutStartingHash}`
}
export { computeDependencyName }
@@ -0,0 +1,12 @@
import { TSESTree } from '@typescript-eslint/types'
import { NodeNameDetails } from './types.js'
/**
* Computes the name details of an identifier.
*
* @param node - The node to compute the name details for.
* @returns An object containing the name, whether it has a private hash, and
* the name without the starting hash.
*/
export declare function computeIdentifierNameDetails(
node: TSESTree.PrivateIdentifier | TSESTree.Identifier | TSESTree.Literal,
): NodeNameDetails
@@ -0,0 +1,34 @@
import { UnreachableCaseError } from '../../utils/unreachable-case-error.js'
import { AST_NODE_TYPES } from '@typescript-eslint/utils'
/**
* Computes the name details of an identifier.
*
* @param node - The node to compute the name details for.
* @returns An object containing the name, whether it has a private hash, and
* the name without the starting hash.
*/
function computeIdentifierNameDetails(node) {
switch (node.type) {
case AST_NODE_TYPES.PrivateIdentifier:
return {
nameWithoutStartingHash: node.name,
name: `#${node.name}`,
hasPrivateHash: true,
}
case AST_NODE_TYPES.Identifier:
return buildNonPrivateHashDetails(node.name)
case AST_NODE_TYPES.Literal:
return buildNonPrivateHashDetails(`${node.value}`)
/* v8 ignore next 2 -- @preserve Exhaustive guard. */
default:
throw new UnreachableCaseError(node)
}
}
function buildNonPrivateHashDetails(name) {
return {
nameWithoutStartingHash: name,
hasPrivateHash: false,
name,
}
}
export { computeIdentifierNameDetails }
@@ -0,0 +1,23 @@
import { RuleContext } from '@typescript-eslint/utils/ts-eslint'
import { TSESTree } from '@typescript-eslint/types'
import { Options } from './types.js'
/**
* Computes the matched context options for a given class node.
*
* @param params - Parameters.
* @param params.matchedAstSelectors - The matched AST selectors for a class
* node.
* @param params.context - The rule context.
* @returns The matched context options or undefined if none match.
*/
export declare function computeMatchedContextOptions<
MessageIds extends string,
>({
matchedAstSelectors,
classElements,
context,
}: {
context: Readonly<RuleContext<MessageIds, Options>>
matchedAstSelectors: ReadonlySet<string>
classElements: TSESTree.ClassElement[]
}): Options[number] | undefined
@@ -0,0 +1,52 @@
import { passesAllNamesMatchPatternFilter } from '../../utils/context-matching/passes-all-names-match-pattern-filter.js'
import { passesAstSelectorFilter } from '../../utils/context-matching/passes-ast-selector-filter.js'
import { computeMethodOrPropertyNameDetails } from './node-info/compute-method-or-property-name-details.js'
import { AST_NODE_TYPES } from '@typescript-eslint/utils'
/**
* Computes the matched context options for a given class node.
*
* @param params - Parameters.
* @param params.matchedAstSelectors - The matched AST selectors for a class
* node.
* @param params.context - The rule context.
* @returns The matched context options or undefined if none match.
*/
function computeMatchedContextOptions({
matchedAstSelectors,
classElements,
context,
}) {
let nodeNames = classElements
.filter(
element =>
element.type !== AST_NODE_TYPES.StaticBlock &&
element.type !== AST_NODE_TYPES.TSIndexSignature,
)
.map(
element =>
computeMethodOrPropertyNameDetails(element, context.sourceCode).name,
)
return context.options.find(options =>
isContextOptionMatching({
matchedAstSelectors,
nodeNames,
options,
}),
)
}
function isContextOptionMatching({ matchedAstSelectors, nodeNames, options }) {
if (!options.useConfigurationIf) {
return true
}
return (
passesAllNamesMatchPatternFilter({
allNamesMatchPattern: options.useConfigurationIf.allNamesMatchPattern,
nodeNames,
}) &&
passesAstSelectorFilter({
matchesAstSelector: options.useConfigurationIf.matchesAstSelector,
matchedAstSelectors,
})
)
}
export { computeMatchedContextOptions }
@@ -0,0 +1,13 @@
import { TSESTree } from '@typescript-eslint/utils'
import { OverloadSignatureGroup } from '../../utils/overload-signature/overload-signature-group.js'
type Method = TSESTree.TSAbstractMethodDefinition | TSESTree.MethodDefinition
/**
* Returns a list of groups of overload signatures.
*
* @param classElements - The class elements to process.
* @returns A list of overload signature groups.
*/
export declare function computeOverloadSignatureGroups(
classElements: TSESTree.ClassElement[],
): OverloadSignatureGroup<Method>[]
export {}
@@ -0,0 +1,62 @@
import { UnreachableCaseError } from '../../utils/unreachable-case-error.js'
import { isSortable } from '../../utils/is-sortable.js'
import { OverloadSignatureGroup } from '../../utils/overload-signature/overload-signature-group.js'
import { AST_NODE_TYPES } from '@typescript-eslint/utils'
/**
* Returns a list of groups of overload signatures.
*
* @param classElements - The class elements to process.
* @returns A list of overload signature groups.
*/
function computeOverloadSignatureGroups(classElements) {
let methods = classElements
.filter(
classElement =>
classElement.type === AST_NODE_TYPES.MethodDefinition ||
classElement.type === AST_NODE_TYPES.TSAbstractMethodDefinition,
)
.filter(classElement => classElement.kind === 'method')
let staticOverloadSignaturesByName = /* @__PURE__ */ new Map()
let overloadSignaturesByName = /* @__PURE__ */ new Map()
for (let method of methods) {
if (method.key.type !== AST_NODE_TYPES.Identifier) {
continue
}
let { name } = method.key
let mapToUse =
method.static ? staticOverloadSignaturesByName : overloadSignaturesByName
let overloadSignaturesArray = mapToUse.get(name)
if (!overloadSignaturesArray) {
overloadSignaturesArray = []
mapToUse.set(name, overloadSignaturesArray)
}
overloadSignaturesArray.push(method)
}
return [
...overloadSignaturesByName.values(),
...staticOverloadSignaturesByName.values(),
]
.filter(isSortable)
.map(buildOverloadSignatureGroup)
}
function buildOverloadSignatureGroup(methods) {
let implementation = methods.find(isMethodImplementation) ?? methods.at(-1)
return new OverloadSignatureGroup({
overloadSignatures: methods.filter(
method => !isMethodImplementation(method),
),
implementation,
})
function isMethodImplementation(method) {
switch (method.value.type) {
case AST_NODE_TYPES.TSEmptyBodyFunctionExpression:
return false
case AST_NODE_TYPES.FunctionExpression:
return true
/* v8 ignore next 2 -- @preserve Exhaustive guard. */
default:
throw new UnreachableCaseError(method.value)
}
}
}
export { computeOverloadSignatureGroups }
@@ -0,0 +1,13 @@
import { TSESTree } from '@typescript-eslint/types'
/**
* Checks whether a class element is supported by the sort-classes rule.
*
* Unknown elements should be ignored to avoid crashes with non-standard parsers
* while letting known elements keep their ordering behavior.
*
* @param member - The class element to check.
* @returns True when the element is a known, supported class member.
*/
export declare function isKnownClassElement(
member: TSESTree.ClassElement,
): boolean
@@ -0,0 +1,27 @@
import '../../utils/assert-is-never.js'
import { AST_NODE_TYPES } from '@typescript-eslint/utils'
/**
* Checks whether a class element is supported by the sort-classes rule.
*
* Unknown elements should be ignored to avoid crashes with non-standard parsers
* while letting known elements keep their ordering behavior.
*
* @param member - The class element to check.
* @returns True when the element is a known, supported class member.
*/
function isKnownClassElement(member) {
switch (member.type) {
case AST_NODE_TYPES.TSAbstractPropertyDefinition:
case AST_NODE_TYPES.TSAbstractMethodDefinition:
case AST_NODE_TYPES.TSAbstractAccessorProperty:
case AST_NODE_TYPES.PropertyDefinition:
case AST_NODE_TYPES.MethodDefinition:
case AST_NODE_TYPES.AccessorProperty:
case AST_NODE_TYPES.TSIndexSignature:
case AST_NODE_TYPES.StaticBlock:
return true
default:
return false
}
}
export { isKnownClassElement }
@@ -0,0 +1,39 @@
import { TSESTree } from '@typescript-eslint/types'
import { Modifier } from '../types.js'
type Property =
| TSESTree.TSAbstractPropertyDefinition
| TSESTree.PropertyDefinition
type Accessor = TSESTree.TSAbstractAccessorProperty | TSESTree.AccessorProperty
type Method = TSESTree.TSAbstractMethodDefinition | TSESTree.MethodDefinition
export declare function computeAccessibilityModifier({
hasPrivateHash,
node,
}: {
node: Accessor | Property | Method
hasPrivateHash: boolean
}): Modifier[]
export declare function computeAbstractModifier(
node: Accessor | Property | Method,
): Modifier[]
export declare function computeAsyncModifier(
node:
| TSESTree.TSEmptyBodyFunctionExpression
| TSESTree.ArrowFunctionExpression
| TSESTree.FunctionExpression,
): Modifier[]
export declare function computeStaticModifier(
node: TSESTree.TSIndexSignature | Accessor | Property | Method,
): Modifier[]
export declare function computeReadonlyModifier(
node: TSESTree.TSIndexSignature | Property,
): Modifier[]
export declare function computeOverrideModifier(
node: Accessor | Property | Method,
): Modifier[]
export declare function computeOptionalModifier(
node: Property | Method,
): Modifier[]
export declare function computeDecoratedModifier(
isDecorated: boolean,
): Modifier[]
export {}
@@ -0,0 +1,71 @@
import { UnreachableCaseError } from '../../../utils/unreachable-case-error.js'
import '../../../utils/assert-is-never.js'
import { AST_NODE_TYPES } from '@typescript-eslint/utils'
function computeAccessibilityModifier({ hasPrivateHash, node }) {
if (hasPrivateHash) {
return ['private']
}
switch (node.accessibility) {
case 'protected':
return ['protected']
case 'private':
return ['private']
case void 0:
case 'public':
return ['public']
default:
node.accessibility
return computeUnhandledAccessibilityModifier(node.accessibility)
}
function computeUnhandledAccessibilityModifier(modifier) {
/* v8 ignore else -- @preserve Unhandled case */
if (modifier === null) {
return ['public']
}
/* v8 ignore next -- @preserve Unhandled case */
throw new Error('Unhandled accessibility modifier')
}
}
function computeAbstractModifier(node) {
switch (node.type) {
case AST_NODE_TYPES.TSAbstractPropertyDefinition:
case AST_NODE_TYPES.TSAbstractMethodDefinition:
case AST_NODE_TYPES.TSAbstractAccessorProperty:
return ['abstract']
case AST_NODE_TYPES.PropertyDefinition:
case AST_NODE_TYPES.MethodDefinition:
case AST_NODE_TYPES.AccessorProperty:
return []
/* v8 ignore next 2 -- @preserve Exhaustive guard. */
default:
throw new UnreachableCaseError(node)
}
}
function computeAsyncModifier(node) {
return node.async ? ['async'] : []
}
function computeStaticModifier(node) {
return node.static ? ['static'] : []
}
function computeReadonlyModifier(node) {
return node.readonly ? ['readonly'] : []
}
function computeOverrideModifier(node) {
return node.override ? ['override'] : []
}
function computeOptionalModifier(node) {
return node.optional ? ['optional'] : []
}
function computeDecoratedModifier(isDecorated) {
return isDecorated ? ['decorated'] : []
}
export {
computeAbstractModifier,
computeAccessibilityModifier,
computeAsyncModifier,
computeDecoratedModifier,
computeOptionalModifier,
computeOverrideModifier,
computeReadonlyModifier,
computeStaticModifier,
}
@@ -0,0 +1,27 @@
import { TSESTree } from '@typescript-eslint/types'
import { TSESLint } from '@typescript-eslint/utils'
import { NodeNameDetails, Modifier, Selector } from '../types.js'
/**
* Computes details related to an accessor property.
*
* @param params - Parameters object.
* @param params.isDecorated - Whether the accessor is decorated.
* @param params.sourceCode - The source code object.
* @param params.accessor - The accessor node to compute information for.
* @returns An object containing various details about the accessor.
*/
export declare function computeAccessorDetails({
isDecorated,
sourceCode,
accessor,
}: {
accessor: TSESTree.TSAbstractAccessorProperty | TSESTree.AccessorProperty
sourceCode: TSESLint.SourceCode
isDecorated: boolean
}): {
nameDetails: NodeNameDetails
dependencyNames: string[]
modifiers: Modifier[]
selectors: Selector[]
isStatic: boolean
}
@@ -0,0 +1,52 @@
import {
computeAbstractModifier,
computeAccessibilityModifier,
computeDecoratedModifier,
computeOverrideModifier,
computeStaticModifier,
} from './common-modifiers.js'
import { computeDependencyName } from '../compute-dependency-name.js'
import { computeMethodOrPropertyNameDetails } from './compute-method-or-property-name-details.js'
/**
* Computes details related to an accessor property.
*
* @param params - Parameters object.
* @param params.isDecorated - Whether the accessor is decorated.
* @param params.sourceCode - The source code object.
* @param params.accessor - The accessor node to compute information for.
* @returns An object containing various details about the accessor.
*/
function computeAccessorDetails({ isDecorated, sourceCode, accessor }) {
let nameDetails = computeMethodOrPropertyNameDetails(accessor, sourceCode)
let modifiers = computeModifiers({
hasPrivateHash: nameDetails.hasPrivateHash,
isDecorated,
accessor,
})
return {
dependencyNames: [
computeDependencyName({
nodeNameWithoutStartingHash: nameDetails.nameWithoutStartingHash,
hasPrivateHash: nameDetails.hasPrivateHash,
isStatic: modifiers.includes('static'),
}),
],
selectors: ['accessor-property'],
isStatic: accessor.static,
nameDetails,
modifiers,
}
}
function computeModifiers({ hasPrivateHash, isDecorated, accessor }) {
return [
...computeStaticModifier(accessor),
...computeAbstractModifier(accessor),
...computeDecoratedModifier(isDecorated),
...computeOverrideModifier(accessor),
...computeAccessibilityModifier({
hasPrivateHash,
node: accessor,
}),
]
}
export { computeAccessorDetails }
@@ -0,0 +1,23 @@
import { TSESTree } from '@typescript-eslint/types'
import { TSESLint } from '@typescript-eslint/utils'
import { Modifier, Selector } from '../types.js'
/**
* Computes details related to an index-signature.
*
* @param params - Parameters object.
* @param params.indexSignature - The index signature node to compute
* information for.
* @param params.sourceCode - The source code object.
* @returns An object containing various details about the index-signature.
*/
export declare function computeIndexSignatureDetails({
indexSignature,
sourceCode,
}: {
indexSignature: TSESTree.TSIndexSignature
sourceCode: TSESLint.SourceCode
}): {
modifiers: Modifier[]
selectors: Selector[]
name: string
}
@@ -0,0 +1,30 @@
import {
computeReadonlyModifier,
computeStaticModifier,
} from './common-modifiers.js'
/**
* Computes details related to an index-signature.
*
* @param params - Parameters object.
* @param params.indexSignature - The index signature node to compute
* information for.
* @param params.sourceCode - The source code object.
* @returns An object containing various details about the index-signature.
*/
function computeIndexSignatureDetails({ indexSignature, sourceCode }) {
return {
name: sourceCode.text.slice(
indexSignature.range.at(0),
indexSignature.typeAnnotation?.range.at(0) ?? indexSignature.range.at(1),
),
modifiers: computeModifiers(indexSignature),
selectors: ['index-signature'],
}
}
function computeModifiers(indexSignature) {
return [
...computeStaticModifier(indexSignature),
...computeReadonlyModifier(indexSignature),
]
}
export { computeIndexSignatureDetails }
@@ -0,0 +1,30 @@
import { TSESTree } from '@typescript-eslint/types'
import { TSESLint } from '@typescript-eslint/utils'
import { NodeNameDetails, Modifier, Selector } from '../types.js'
/**
* Computes details related to a method.
*
* @param params - Parameters object.
* @param params.isDecorated - Whether the accessor is decorated.
* @param params.method - The method node to compute information for.
* @param params.sourceCode - The source code object.
* @param params.hasParentDeclare - Whether the parent class is a declare class.
* @returns An object containing various details about the method.
*/
export declare function computeMethodDetails({
hasParentDeclare,
isDecorated,
sourceCode,
method,
}: {
method: TSESTree.TSAbstractMethodDefinition | TSESTree.MethodDefinition
sourceCode: TSESLint.SourceCode
hasParentDeclare: boolean
isDecorated: boolean
}): {
addSafetySemicolonWhenInline: boolean
nameDetails: NodeNameDetails
modifiers: Modifier[]
selectors: Selector[]
isStatic: boolean
}
@@ -0,0 +1,88 @@
import { UnreachableCaseError } from '../../../utils/unreachable-case-error.js'
import {
computeAbstractModifier,
computeAccessibilityModifier,
computeAsyncModifier,
computeDecoratedModifier,
computeOptionalModifier,
computeOverrideModifier,
computeStaticModifier,
} from './common-modifiers.js'
import { computeMethodOrPropertyNameDetails } from './compute-method-or-property-name-details.js'
import { AST_NODE_TYPES } from '@typescript-eslint/utils'
/**
* Computes details related to a method.
*
* @param params - Parameters object.
* @param params.isDecorated - Whether the accessor is decorated.
* @param params.method - The method node to compute information for.
* @param params.sourceCode - The source code object.
* @param params.hasParentDeclare - Whether the parent class is a declare class.
* @returns An object containing various details about the method.
*/
function computeMethodDetails({
hasParentDeclare,
isDecorated,
sourceCode,
method,
}) {
let nameDetails = computeMethodOrPropertyNameDetails(method, sourceCode)
return {
modifiers: computeModifiers({
hasPrivateHash: nameDetails.hasPrivateHash,
isDecorated,
method,
}),
addSafetySemicolonWhenInline: shouldAddSafetySemicolonWhenInline({
hasParentDeclare,
method,
}),
selectors: computeSelectors(method),
isStatic: method.static,
nameDetails,
}
}
function computeSelectors(method) {
return [...computeSetterOrConstructorSelector(), 'method']
function computeSetterOrConstructorSelector() {
switch (method.kind) {
case 'constructor':
return ['constructor']
case 'method':
return []
case 'set':
return ['set-method']
case 'get':
return ['get-method']
/* v8 ignore next 2 -- @preserve Exhaustive guard. */
default:
throw new UnreachableCaseError(method.kind)
}
}
}
function computeModifiers({ hasPrivateHash, isDecorated, method }) {
return [
...computeStaticModifier(method),
...computeAbstractModifier(method),
...computeDecoratedModifier(isDecorated),
...computeOverrideModifier(method),
...computeAccessibilityModifier({
hasPrivateHash,
node: method,
}),
...computeOptionalModifier(method),
...computeAsyncModifier(method.value),
]
}
function shouldAddSafetySemicolonWhenInline({ hasParentDeclare, method }) {
switch (method.type) {
case AST_NODE_TYPES.TSAbstractMethodDefinition:
return true
case AST_NODE_TYPES.MethodDefinition:
return hasParentDeclare
/* v8 ignore next 2 -- @preserve Exhaustive guard. */
default:
throw new UnreachableCaseError(method)
}
}
export { computeMethodDetails }
@@ -0,0 +1,21 @@
import { TSESTree } from '@typescript-eslint/types'
import { TSESLint } from '@typescript-eslint/utils'
import { NodeNameDetails } from '../types.js'
/**
* Computes the name details of a method or property node.
*
* @param node - The method or property node to compute the name for.
* @param sourceCode - The ESLint source code object.
* @returns An object containing the name, whether it has a private hash, and
* the name without the starting hash.
*/
export declare function computeMethodOrPropertyNameDetails(
node:
| TSESTree.TSAbstractPropertyDefinition
| TSESTree.TSAbstractMethodDefinition
| TSESTree.TSAbstractAccessorProperty
| TSESTree.PropertyDefinition
| TSESTree.MethodDefinition
| TSESTree.AccessorProperty,
sourceCode: TSESLint.SourceCode,
): NodeNameDetails
@@ -0,0 +1,25 @@
import { computeIdentifierNameDetails } from '../compute-identifier-name-details.js'
import { AST_NODE_TYPES } from '@typescript-eslint/utils'
/**
* Computes the name details of a method or property node.
*
* @param node - The method or property node to compute the name for.
* @param sourceCode - The ESLint source code object.
* @returns An object containing the name, whether it has a private hash, and
* the name without the starting hash.
*/
function computeMethodOrPropertyNameDetails(node, sourceCode) {
switch (node.key.type) {
case AST_NODE_TYPES.PrivateIdentifier:
case AST_NODE_TYPES.Identifier:
case AST_NODE_TYPES.Literal:
return computeIdentifierNameDetails(node.key)
default:
return {
nameWithoutStartingHash: sourceCode.getText(node.key),
name: sourceCode.getText(node.key),
hasPrivateHash: false,
}
}
}
export { computeMethodOrPropertyNameDetails }
@@ -0,0 +1,41 @@
import { TSESTree } from '@typescript-eslint/types'
import { TSESLint } from '@typescript-eslint/utils'
import { NodeNameDetails, Modifier, Selector } from '../types.js'
import { RegexOption } from '../../../types/common-options.js'
/**
* Computes details related to a property.
*
* @param params - Parameters object.
* @param params.isDecorated - Whether the accessor is decorated.
* @param params.property - The property node to compute information for.
* @param params.ignoreCallbackDependenciesPatterns - Patterns to ignore when
* computing dependencies.
* @param params.useExperimentalDependencyDetection - Whether to use
* experimental dependency detection.
* @param params.sourceCode - The source code object.
* @param params.className - The name of the class containing the property.
* @returns An object containing various details about the property.
*/
export declare function computePropertyDetails({
ignoreCallbackDependenciesPatterns,
useExperimentalDependencyDetection,
isDecorated,
sourceCode,
className,
property,
}: {
property: TSESTree.TSAbstractPropertyDefinition | TSESTree.PropertyDefinition
ignoreCallbackDependenciesPatterns: RegexOption
useExperimentalDependencyDetection: boolean
sourceCode: TSESLint.SourceCode
className: undefined | string
isDecorated: boolean
}): {
memberValue: undefined | string
nameDetails: NodeNameDetails
dependencyNames: string[]
dependencies: string[]
modifiers: Modifier[]
selectors: Selector[]
isStatic: boolean
}
@@ -0,0 +1,127 @@
import {
computeAbstractModifier,
computeAccessibilityModifier,
computeAsyncModifier,
computeDecoratedModifier,
computeOptionalModifier,
computeOverrideModifier,
computeReadonlyModifier,
computeStaticModifier,
} from './common-modifiers.js'
import { computeDependencyName } from '../compute-dependency-name.js'
import { computeDependencies } from '../compute-dependencies.js'
import { computeMethodOrPropertyNameDetails } from './compute-method-or-property-name-details.js'
import { AST_NODE_TYPES } from '@typescript-eslint/utils'
/**
* Computes details related to a property.
*
* @param params - Parameters object.
* @param params.isDecorated - Whether the accessor is decorated.
* @param params.property - The property node to compute information for.
* @param params.ignoreCallbackDependenciesPatterns - Patterns to ignore when
* computing dependencies.
* @param params.useExperimentalDependencyDetection - Whether to use
* experimental dependency detection.
* @param params.sourceCode - The source code object.
* @param params.className - The name of the class containing the property.
* @returns An object containing various details about the property.
*/
function computePropertyDetails({
ignoreCallbackDependenciesPatterns,
useExperimentalDependencyDetection,
isDecorated,
sourceCode,
className,
property,
}) {
let nameDetails = computeMethodOrPropertyNameDetails(property, sourceCode)
let modifiers = computeModifiers({
hasPrivateHash: nameDetails.hasPrivateHash,
isDecorated,
property,
})
return {
dependencyNames: [
computeDependencyName({
nodeNameWithoutStartingHash: nameDetails.nameWithoutStartingHash,
hasPrivateHash: nameDetails.hasPrivateHash,
isStatic: modifiers.includes('static'),
}),
],
dependencies: computePropertyDependencies({
ignoreCallbackDependenciesPatterns,
useExperimentalDependencyDetection,
className,
property,
}),
memberValue:
!isFunctionExpression(property.value) && property.value ?
sourceCode.getText(property.value)
: void 0,
selectors: computeSelectors(property),
isStatic: property.static,
nameDetails,
modifiers,
}
}
function computeModifiers({ hasPrivateHash, isDecorated, property }) {
return [
...computeStaticModifier(property),
...computeDeclareModifier(),
...computeAbstractModifier(property),
...computeDecoratedModifier(isDecorated),
...computeOverrideModifier(property),
...computeReadonlyModifier(property),
...computeAccessibilityModifier({
hasPrivateHash,
node: property,
}),
...computeOptionalModifier(property),
...computeAsyncModifierIfFunctionProperty(),
]
function computeDeclareModifier() {
return property.declare ? ['declare'] : []
}
function computeAsyncModifierIfFunctionProperty() {
if (!isFunctionExpression(property.value)) {
return []
}
return computeAsyncModifier(property.value)
}
}
function computePropertyDependencies({
ignoreCallbackDependenciesPatterns,
useExperimentalDependencyDetection,
className,
property,
}) {
if (isFunctionExpression(property.value)) {
return []
}
if (!property.value) {
return []
}
return computeDependencies({
ignoreCallbackDependenciesPatterns,
useExperimentalDependencyDetection,
isMemberStatic: property.static,
expression: property.value,
className,
})
}
function computeSelectors(property) {
return [...computeFunctionPropertySelector(), 'property']
function computeFunctionPropertySelector() {
return isFunctionExpression(property.value) ? ['function-property'] : []
}
}
function isFunctionExpression(node) {
if (!node) {
return false
}
return (
node.type === AST_NODE_TYPES.ArrowFunctionExpression ||
node.type === AST_NODE_TYPES.FunctionExpression
)
}
export { computePropertyDetails }
@@ -0,0 +1,30 @@
import { TSESTree } from '@typescript-eslint/types'
import { RegexOption } from '../../../types/common-options.js'
import { Modifier, Selector } from '../types.js'
/**
* Computes details related to a static block.
*
* @param params - Parameters object.
* @param params.staticBlock - The static block node to compute information for.
* @param params.ignoreCallbackDependenciesPatterns - Patterns to ignore when
* computing dependencies.
* @param params.useExperimentalDependencyDetection - Whether to use
* experimental dependency detection.
* @param params.className - The name of the class containing the property.
* @returns An object containing various details about the static block.
*/
export declare function computeStaticBlockDetails({
ignoreCallbackDependenciesPatterns,
useExperimentalDependencyDetection,
staticBlock,
className,
}: {
ignoreCallbackDependenciesPatterns: RegexOption
useExperimentalDependencyDetection: boolean
staticBlock: TSESTree.StaticBlock
className: undefined | string
}): {
dependencies: string[]
selectors: Selector[]
modifiers: Modifier[]
}
@@ -0,0 +1,32 @@
import { computeDependencies } from '../compute-dependencies.js'
/**
* Computes details related to a static block.
*
* @param params - Parameters object.
* @param params.staticBlock - The static block node to compute information for.
* @param params.ignoreCallbackDependenciesPatterns - Patterns to ignore when
* computing dependencies.
* @param params.useExperimentalDependencyDetection - Whether to use
* experimental dependency detection.
* @param params.className - The name of the class containing the property.
* @returns An object containing various details about the static block.
*/
function computeStaticBlockDetails({
ignoreCallbackDependenciesPatterns,
useExperimentalDependencyDetection,
staticBlock,
className,
}) {
return {
dependencies: computeDependencies({
ignoreCallbackDependenciesPatterns,
useExperimentalDependencyDetection,
expression: staticBlock,
isMemberStatic: true,
className,
}),
selectors: ['static-block'],
modifiers: [],
}
}
export { computeStaticBlockDetails }
@@ -0,0 +1,13 @@
import { TSESTree } from '@typescript-eslint/types'
import { TSESLint } from '@typescript-eslint/utils'
import { MessageId, Options } from './types.js'
export declare let defaultOptions: Required<Options[number]>
export declare function sortClass({
matchedAstSelectors,
context,
node,
}: {
context: Readonly<TSESLint.RuleContext<MessageId, Options>>
matchedAstSelectors: ReadonlySet<string>
node: TSESTree.ClassBody
}): void
@@ -0,0 +1,324 @@
import { UnreachableCaseError } from '../../utils/unreachable-case-error.js'
import { getGroupIndex } from '../../utils/get-group-index.js'
import { populateSortingNodeGroupsWithDependencies } from '../../utils/populate-sorting-node-groups-with-dependencies.js'
import { validateNewlinesAndPartitionConfiguration } from '../../utils/validate-newlines-and-partition-configuration.js'
import { defaultComparatorByOptionsComputer } from '../../utils/compare/default-comparator-by-options-computer.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 { isSortable } from '../../utils/is-sortable.js'
import { complete } from '../../utils/complete.js'
import { getNodeDecorators } from '../../utils/get-node-decorators.js'
import { getDecoratorName } from '../../utils/get-decorator-name.js'
import {
DEPENDENCY_ORDER_ERROR_ID,
EXTRA_SPACING_ERROR_ID,
GROUP_ORDER_ERROR_ID,
MISSED_SPACING_ERROR_ID,
ORDER_ERROR_ID,
allModifiers,
allSelectors,
} from './types.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 { computeIndexSignatureDetails } from './node-info/compute-index-signature-details.js'
import { computeDependenciesBySortingNode } from './compute-dependencies-by-sorting-node.js'
import { computeStaticBlockDetails } from './node-info/compute-static-block-details.js'
import { computeOverloadSignatureGroups } from './compute-overload-signature-groups.js'
import { computeMatchedContextOptions } from './compute-matched-context-options.js'
import { computePropertyDetails } from './node-info/compute-property-details.js'
import { computeAccessorDetails } from './node-info/compute-accessor-details.js'
import { computeMethodDetails } from './node-info/compute-method-details.js'
import { isKnownClassElement } from './is-known-class-element.js'
import { AST_NODE_TYPES } from '@typescript-eslint/utils'
/**
* Cache computed groups by modifiers and selectors for performance.
*/
var cachedGroupsByModifiersAndSelectors = /* @__PURE__ */ new Map()
var defaultOptions = {
groups: [
'index-signature',
['static-property', 'static-accessor-property'],
['static-get-method', 'static-set-method'],
['protected-static-property', 'protected-static-accessor-property'],
['protected-static-get-method', 'protected-static-set-method'],
['private-static-property', 'private-static-accessor-property'],
['private-static-get-method', 'private-static-set-method'],
'static-block',
['property', 'accessor-property'],
['get-method', 'set-method'],
['protected-property', 'protected-accessor-property'],
['protected-get-method', 'protected-set-method'],
['private-property', 'private-accessor-property'],
['private-get-method', 'private-set-method'],
'constructor',
['static-method', 'static-function-property'],
['protected-static-method', 'protected-static-function-property'],
['private-static-method', 'private-static-function-property'],
['method', 'function-property'],
['protected-method', 'protected-function-property'],
['private-method', 'private-function-property'],
'unknown',
],
useExperimentalDependencyDetection: true,
ignoreCallbackDependenciesPatterns: [],
newlinesBetweenOverloadSignatures: 0,
fallbackSort: { type: 'unsorted' },
newlinesInside: 'newlinesBetween',
partitionByComment: false,
partitionByNewLine: false,
newlinesBetween: 'ignore',
specialCharacters: 'keep',
useConfigurationIf: {},
type: 'alphabetical',
ignoreCase: true,
customGroups: [],
locales: 'en-US',
alphabet: '',
order: 'asc',
}
function sortClass({ matchedAstSelectors, context, node }) {
let classElements = node.body.filter(isKnownClassElement)
if (!isSortable(classElements)) {
return
}
let settings = getSettings(context.settings)
let options = complete(
computeMatchedContextOptions({
matchedAstSelectors,
classElements,
context,
}),
settings,
defaultOptions,
)
validateCustomSortConfiguration(options)
validateGroupsConfiguration({
modifiers: allModifiers,
selectors: allSelectors,
options,
})
validateNewlinesAndPartitionConfiguration(options)
let { sourceCode, id } = context
let eslintDisabledLines = getEslintDisabledLines({
ruleName: id,
sourceCode,
})
let optionsByGroupIndexComputer = buildOptionsByGroupIndexComputer(options)
let overloadSignatureNewlinesBetweenValueGetter =
buildOverloadSignatureNewlinesBetweenValueGetter(
options.newlinesBetweenOverloadSignatures,
)
let className = node.parent.id?.name
let sortingNodeGroupsWithoutOverloadSignature = classElements.reduce(
(accumulator, member) => {
let dependencies = []
let isDecorated = false
let decorators = []
if ('decorators' in member) {
decorators = getNodeDecorators(member).map(decorator =>
getDecoratorName({
sourceCode,
decorator,
}),
)
isDecorated = decorators.length > 0
}
let addSafetySemicolonWhenInline
let dependencyNames
let name
let nameDetails
let memberValue
let isStatic
let modifiers
let selectors
switch (member.type) {
case AST_NODE_TYPES.TSAbstractPropertyDefinition:
case AST_NODE_TYPES.PropertyDefinition:
addSafetySemicolonWhenInline = true
;({
dependencyNames,
dependencies,
memberValue,
nameDetails,
modifiers,
selectors,
isStatic,
} = computePropertyDetails({
ignoreCallbackDependenciesPatterns:
options.ignoreCallbackDependenciesPatterns,
useExperimentalDependencyDetection:
options.useExperimentalDependencyDetection,
property: member,
isDecorated,
sourceCode,
className,
}))
;({ name } = nameDetails)
break
case AST_NODE_TYPES.TSAbstractMethodDefinition:
case AST_NODE_TYPES.MethodDefinition:
dependencyNames = []
;({
addSafetySemicolonWhenInline,
nameDetails,
selectors,
modifiers,
isStatic,
} = computeMethodDetails({
hasParentDeclare: node.parent.declare,
method: member,
isDecorated,
sourceCode,
}))
;({ name } = nameDetails)
break
case AST_NODE_TYPES.TSAbstractAccessorProperty:
case AST_NODE_TYPES.AccessorProperty:
addSafetySemicolonWhenInline = true
;({ dependencyNames, nameDetails, selectors, modifiers, isStatic } =
computeAccessorDetails({
accessor: member,
isDecorated,
sourceCode,
}))
;({ name } = nameDetails)
break
case AST_NODE_TYPES.TSIndexSignature:
addSafetySemicolonWhenInline = true
dependencyNames = []
nameDetails = null
isStatic = false
;({ modifiers, selectors, name } = computeIndexSignatureDetails({
indexSignature: member,
sourceCode,
}))
break
case AST_NODE_TYPES.StaticBlock:
addSafetySemicolonWhenInline = false
dependencyNames = []
name = 'static'
nameDetails = null
isStatic = true
;({ dependencies, selectors, modifiers } = computeStaticBlockDetails({
useExperimentalDependencyDetection:
options.useExperimentalDependencyDetection,
ignoreCallbackDependenciesPatterns:
options.ignoreCallbackDependenciesPatterns,
staticBlock: member,
className,
}))
break
/* v8 ignore next 2 -- @preserve Exhaustive guard. */
default:
throw new UnreachableCaseError(member)
}
let group = computeGroup({
customGroupMatcher: customGroup =>
doesCustomGroupMatch({
elementValue: memberValue,
elementName: name,
customGroup,
decorators,
modifiers,
selectors,
}),
predefinedGroups: generatePredefinedGroups({
cache: cachedGroupsByModifiersAndSelectors,
selectors,
modifiers,
}),
options,
})
let sortingNode = {
isEslintDisabled: isNodeEslintDisabled(member, eslintDisabledLines),
size: rangeToDiff(member, sourceCode),
addSafetySemicolonWhenInline,
dependencyNames,
node: member,
dependencies,
nameDetails,
isStatic,
group,
name,
}
let lastSortingNode = accumulator.at(-1)?.at(-1)
if (
shouldPartition({
lastSortingNode,
sortingNode,
sourceCode,
options,
})
) {
accumulator.push([])
}
accumulator.at(-1).push({
...sortingNode,
partitionId: accumulator.length,
})
return accumulator
},
[[]],
)
let sortingNodeGroups = populateSortingNodeGroupsWithOverloadSignature({
overloadSignatureGroups: computeOverloadSignatureGroups(classElements),
sortingNodeGroups: sortingNodeGroupsWithoutOverloadSignature,
})
if (options.useExperimentalDependencyDetection) {
sortingNodeGroups = populateSortingNodeGroupsWithDependencies({
dependenciesBySortingNode: computeDependenciesBySortingNode({
ignoreCallbackDependenciesPatterns:
options.ignoreCallbackDependenciesPatterns,
sortingNodes: sortingNodeGroups.flat(),
classBody: node,
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({
isNodeIgnored: sortingNode =>
getGroupIndex(options.groups, sortingNode) ===
options.groups.length,
comparatorByOptionsComputer: defaultComparatorByOptionsComputer,
optionsByGroupIndexComputer,
ignoreEslintDisabledNodes,
nodes: sortingNodeGroup,
groups: options.groups,
}),
),
{ ignoreEslintDisabledNodes },
)
}
}
export { defaultOptions, sortClass }
@@ -0,0 +1,155 @@
import { JSONSchema4 } from '@typescript-eslint/utils/json-schema'
import { TSESTree } from '@typescript-eslint/types'
import { SortingNodeWithDependencies } from '../../utils/sort-nodes-by-dependencies.js'
import { NewlinesBetweenOption } from '../../types/common-groups-options.js'
import { RegexOption, TypeOption } from '../../types/common-options.js'
import { AllCommonOptions } from '../../types/all-common-options.js'
export type MessageId =
| typeof DEPENDENCY_ORDER_ERROR_ID
| typeof MISSED_SPACING_ERROR_ID
| typeof EXTRA_SPACING_ERROR_ID
| typeof GROUP_ORDER_ERROR_ID
| typeof ORDER_ERROR_ID
export declare const ORDER_ERROR_ID = 'unexpectedClassesOrder'
export declare const GROUP_ORDER_ERROR_ID = 'unexpectedClassesGroupOrder'
export declare const EXTRA_SPACING_ERROR_ID = 'extraSpacingBetweenClassMembers'
export declare const MISSED_SPACING_ERROR_ID =
'missedSpacingBetweenClassMembers'
export declare const DEPENDENCY_ORDER_ERROR_ID =
'unexpectedClassesDependencyOrder'
/**
* Configuration options for the sort-classes rule.
*
* This rule enforces consistent ordering of class members (properties, methods,
* constructors, etc.) to improve code readability and maintainability.
*/
export type Options = Partial<
{
/**
* Conditional configuration based on pattern matching.
*/
useConfigurationIf: {
/**
* Regular expression pattern to match against all class element names.
*/
allNamesMatchPattern?: RegexOption
/**
* AST selector to match against ClassBody nodes.
*/
matchesAstSelector?: string
}
/**
* Determines how many newlines should be placed between overload signatures
* of the same method.
*/
newlinesBetweenOverloadSignatures: NewlinesBetweenOption
/**
* Regex patterns for function names whose callback argument dependencies
* are ignored during class-member sorting. Dependencies inside these
* callbacks won't influence the ordering.
*/
ignoreCallbackDependenciesPatterns: RegexOption
/**
* Enables experimental dependency detection.
*/
useExperimentalDependencyDetection: boolean
} & AllCommonOptions<
TypeOption,
AdditionalSortOptions,
CustomGroupMatchOptions
>
>[]
export interface SortClassesSortingNode extends SortingNodeWithDependencies<TSESTree.ClassElement> {
overloadSignatureImplementation:
| TSESTree.TSAbstractMethodDefinition
| TSESTree.MethodDefinition
| null
nameDetails: NodeNameDetails | null
isStatic: boolean
}
export interface NodeNameDetails {
nameWithoutStartingHash: string
hasPrivateHash: boolean
name: string
}
/**
* Union type of all available class member selectors. Used to identify and
* categorize different types of class members.
*/
export type Selector = (typeof allSelectors)[number]
/**
* Union type of all available class member modifiers. Includes access
* modifiers, async, static, abstract, and other TypeScript modifiers.
*/
export type Modifier = (typeof allModifiers)[number]
/**
* Defines a custom group configuration for class members.
*
* Allows categorizing class members based on their selector type (method,
* property, etc.) and various patterns matching their names, values, or
* decorators.
*/
interface CustomGroupMatchOptions {
/**
* Pattern to match decorator names (e.g., '@Component').
*/
decoratorNamePattern?: RegexOption
/**
* Pattern to match the value of the member (for properties with
* initializers).
*/
elementValuePattern?: RegexOption
/**
* List of modifiers that members must have to be included in this group.
*/
modifiers?: Modifier[]
/**
* The type of class member this group applies to.
*/
selector?: Selector
}
type AdditionalSortOptions = object
/**
* Complete list of available class member selectors. Used for validation and
* JSON schema generation.
*/
export declare let allSelectors: readonly [
'accessor-property',
'index-signature',
'constructor',
'static-block',
'get-method',
'set-method',
'function-property',
'property',
'method',
]
/**
* Complete list of available class member modifiers. Used for validation and
* JSON schema generation.
*/
export declare let allModifiers: readonly [
'async',
'protected',
'private',
'public',
'static',
'abstract',
'override',
'readonly',
'decorated',
'declare',
'optional',
]
/**
* Additional custom group match options JSON schema. Used by ESLint to validate
* rule options at configuration time.
*
* Note: Ideally, we should generate as many schemas as there are selectors, and
* ensure that users do not enter invalid modifiers for a given selector.
*/
export declare let additionalCustomGroupMatchOptionsJsonSchema: Record<
string,
JSONSchema4
>
export {}
@@ -0,0 +1,65 @@
import { buildRegexJsonSchema } from '../../utils/json-schemas/common-json-schemas.js'
import {
buildCustomGroupModifiersJsonSchema,
buildCustomGroupSelectorJsonSchema,
} from '../../utils/json-schemas/common-groups-json-schemas.js'
var ORDER_ERROR_ID = 'unexpectedClassesOrder'
var GROUP_ORDER_ERROR_ID = 'unexpectedClassesGroupOrder'
var EXTRA_SPACING_ERROR_ID = 'extraSpacingBetweenClassMembers'
var MISSED_SPACING_ERROR_ID = 'missedSpacingBetweenClassMembers'
var DEPENDENCY_ORDER_ERROR_ID = 'unexpectedClassesDependencyOrder'
/**
* Complete list of available class member selectors. Used for validation and
* JSON schema generation.
*/
var allSelectors = [
'accessor-property',
'index-signature',
'constructor',
'static-block',
'get-method',
'set-method',
'function-property',
'property',
'method',
]
/**
* Complete list of available class member modifiers. Used for validation and
* JSON schema generation.
*/
var allModifiers = [
'async',
'protected',
'private',
'public',
'static',
'abstract',
'override',
'readonly',
'decorated',
'declare',
'optional',
]
/**
* Additional custom group match options JSON schema. Used by ESLint to validate
* rule options at configuration time.
*
* Note: Ideally, we should generate as many schemas as there are selectors, and
* ensure that users do not enter invalid modifiers for a given selector.
*/
var additionalCustomGroupMatchOptionsJsonSchema = {
modifiers: buildCustomGroupModifiersJsonSchema(allModifiers),
selector: buildCustomGroupSelectorJsonSchema(allSelectors),
decoratorNamePattern: buildRegexJsonSchema(),
elementValuePattern: buildRegexJsonSchema(),
}
export {
DEPENDENCY_ORDER_ERROR_ID,
EXTRA_SPACING_ERROR_ID,
GROUP_ORDER_ERROR_ID,
MISSED_SPACING_ERROR_ID,
ORDER_ERROR_ID,
additionalCustomGroupMatchOptionsJsonSchema,
allModifiers,
allSelectors,
}
@@ -0,0 +1,21 @@
import { Options } from './sort-decorators/types.js'
declare const ORDER_ERROR_ID = 'unexpectedDecoratorsOrder'
declare const GROUP_ORDER_ERROR_ID = 'unexpectedDecoratorsGroupOrder'
declare const EXTRA_SPACING_ERROR_ID = 'extraSpacingBetweenDecorators'
declare const MISSED_SPACING_ERROR_ID = 'missedSpacingBetweenDecorators'
type MessageId =
| typeof MISSED_SPACING_ERROR_ID
| typeof EXTRA_SPACING_ERROR_ID
| typeof GROUP_ORDER_ERROR_ID
| typeof ORDER_ERROR_ID
declare const _default: import('@typescript-eslint/utils/ts-eslint').RuleModule<
MessageId,
Options,
{
recommended?: boolean
},
import('@typescript-eslint/utils/ts-eslint').RuleListener
> & {
name: string
}
export default _default
@@ -0,0 +1,261 @@
import { buildCommonJsonSchemas } from '../utils/json-schemas/common-json-schemas.js'
import { buildCommonGroupsJsonSchemas } from '../utils/json-schemas/common-groups-json-schemas.js'
import {
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 { validateNewlinesAndPartitionConfiguration } from '../utils/validate-newlines-and-partition-configuration.js'
import { defaultComparatorByOptionsComputer } from '../utils/compare/default-comparator-by-options-computer.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 { 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 { isSortable } from '../utils/is-sortable.js'
import { complete } from '../utils/complete.js'
import { createEslintRule } from '../utils/create-eslint-rule.js'
import { getNodeDecorators } from '../utils/get-node-decorators.js'
import { getDecoratorName } from '../utils/get-decorator-name.js'
import { AST_NODE_TYPES } from '@typescript-eslint/utils'
var ORDER_ERROR_ID = 'unexpectedDecoratorsOrder'
var GROUP_ORDER_ERROR_ID = 'unexpectedDecoratorsGroupOrder'
var EXTRA_SPACING_ERROR_ID = 'extraSpacingBetweenDecorators'
var MISSED_SPACING_ERROR_ID = 'missedSpacingBetweenDecorators'
var defaultOptions = {
fallbackSort: { type: 'unsorted' },
newlinesInside: 'newlinesBetween',
specialCharacters: 'keep',
partitionByComment: false,
partitionByNewLine: false,
newlinesBetween: 'ignore',
sortOnProperties: true,
sortOnParameters: true,
sortOnAccessors: true,
type: 'alphabetical',
sortOnClasses: true,
sortOnMethods: true,
ignoreCase: true,
customGroups: [],
locales: 'en-US',
alphabet: '',
order: 'asc',
groups: [],
}
var sort_decorators_default = createEslintRule({
meta: {
schema: {
items: {
properties: {
...buildCommonJsonSchemas(),
...buildCommonGroupsJsonSchemas(),
sortOnParameters: {
description:
'Controls whether sorting should be enabled for method parameter decorators.',
type: 'boolean',
},
sortOnProperties: {
description:
'Controls whether sorting should be enabled for class property decorators.',
type: 'boolean',
},
sortOnAccessors: {
description:
'Controls whether sorting should be enabled for class accessor decorators.',
type: 'boolean',
},
sortOnMethods: {
description:
'Controls whether sorting should be enabled for class method decorators.',
type: 'boolean',
},
sortOnClasses: {
description:
'Controls whether sorting should be enabled for class decorators.',
type: 'boolean',
},
partitionByComment: partitionByCommentJsonSchema,
partitionByNewLine: partitionByNewLineJsonSchema,
},
additionalProperties: false,
type: 'object',
},
uniqueItems: true,
type: 'array',
},
messages: {
[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-decorators',
description: 'Enforce sorted decorators.',
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: [],
selectors: [],
options,
})
validateNewlinesAndPartitionConfiguration(options)
return {
Decorator: decorator => {
if (!options.sortOnParameters) {
return
}
if (
'decorators' in decorator.parent &&
decorator.parent.type === AST_NODE_TYPES.Identifier &&
decorator.parent.parent.type === AST_NODE_TYPES.FunctionExpression
) {
let { decorators } = decorator.parent
if (decorator !== decorators[0]) {
return
}
sortDecorators(context, options, decorators)
}
},
PropertyDefinition: propertyDefinition => {
if (options.sortOnProperties) {
sortDecorators(
context,
options,
getNodeDecorators(propertyDefinition),
)
}
},
AccessorProperty: accessorDefinition => {
if (options.sortOnAccessors) {
sortDecorators(
context,
options,
getNodeDecorators(accessorDefinition),
)
}
},
MethodDefinition: methodDefinition => {
if (options.sortOnMethods) {
sortDecorators(context, options, getNodeDecorators(methodDefinition))
}
},
ClassDeclaration: declaration => {
if (options.sortOnClasses) {
sortDecorators(context, options, getNodeDecorators(declaration))
}
},
}
},
defaultOptions: [defaultOptions],
name: 'sort-decorators',
})
/**
* Sorts decorators attached to a class, method, or property.
*
* Processes the decorators, groups them according to options, and reports any
* ordering errors found. Handles partitioning by comments and newlines.
*
* @param context - The ESLint rule context.
* @param options - The sorting options for decorators.
* @param decorators - Array of decorator nodes to sort.
*/
function sortDecorators(context, options, decorators) {
if (!isSortable(decorators)) {
return
}
let { sourceCode, id } = context
let eslintDisabledLines = getEslintDisabledLines({
ruleName: id,
sourceCode,
})
let optionsByGroupIndexComputer = buildOptionsByGroupIndexComputer(options)
let formattedMembers = decorators.reduce(
(accumulator, decorator) => {
let name = getDecoratorName({
sourceCode,
decorator,
})
let group = computeGroup({
customGroupMatcher: customGroup =>
doesCustomGroupMatch({
elementName: name,
selectors: [],
modifiers: [],
customGroup,
}),
predefinedGroups: [],
options,
})
let sortingNode = {
isEslintDisabled: isNodeEslintDisabled(decorator, eslintDisabledLines),
size: rangeToDiff(decorator, sourceCode),
node: decorator,
group,
name,
}
let lastSortingNode = accumulator.at(-1)?.at(-1)
if (
shouldPartition({
lastSortingNode,
sortingNode,
sourceCode,
options,
})
) {
accumulator.push([])
}
accumulator.at(-1).push({
...sortingNode,
partitionId: accumulator.length,
})
return accumulator
},
[[]],
)
function sortNodesExcludingEslintDisabled(ignoreEslintDisabledNodes) {
return formattedMembers.flatMap(nodes =>
sortNodesByGroups({
comparatorByOptionsComputer: defaultComparatorByOptionsComputer,
optionsByGroupIndexComputer,
ignoreEslintDisabledNodes,
groups: options.groups,
nodes,
}),
)
}
let nodes = formattedMembers.flat()
reportAllErrors({
availableMessageIds: {
missedSpacingBetweenMembers: MISSED_SPACING_ERROR_ID,
extraSpacingBetweenMembers: EXTRA_SPACING_ERROR_ID,
unexpectedGroupOrder: GROUP_ORDER_ERROR_ID,
unexpectedOrder: ORDER_ERROR_ID,
},
ignoreFirstNodeHighestBlockComment: true,
sortNodesExcludingEslintDisabled,
options,
context,
nodes,
})
}
export { sort_decorators_default as default }
@@ -0,0 +1,24 @@
import { TSESTree } from '@typescript-eslint/types'
import { AllCommonOptions } from '../../types/all-common-options.js'
import { TypeOption } from '../../types/common-options.js'
import { SortingNode } from '../../types/sorting-node.js'
export type Options = Partial<
{
sortOnParameters: boolean
sortOnProperties: boolean
sortOnAccessors: boolean
sortOnMethods: boolean
sortOnClasses: boolean
} & AllCommonOptions<
TypeOption,
AdditionalSortOptions,
CustomGroupMatchOptions
>
>[]
export type SortDecoratorsSortingNode = SortingNode<TSESTree.Decorator>
/**
* Match options for a custom group.
*/
type CustomGroupMatchOptions = object
type AdditionalSortOptions = object
export {}
@@ -0,0 +1,12 @@
import { MessageId, Options } from './sort-enums/types.js'
declare const _default: import('@typescript-eslint/utils/ts-eslint').RuleModule<
MessageId,
Options,
{
recommended?: boolean
},
import('@typescript-eslint/utils/ts-eslint').RuleListener
> & {
name: string
}
export default _default
@@ -0,0 +1,86 @@
import {
buildCommonJsonSchemas,
buildUseConfigurationIfJsonSchema,
matchesAstSelectorJsonSchema,
useExperimentalDependencyDetectionJsonSchema,
} from '../utils/json-schemas/common-json-schemas.js'
import { buildCommonGroupsJsonSchemas } from '../utils/json-schemas/common-groups-json-schemas.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 { buildAstListeners } from '../utils/build-ast-listeners.js'
import { createEslintRule } from '../utils/create-eslint-rule.js'
import {
DEPENDENCY_ORDER_ERROR_ID,
EXTRA_SPACING_ERROR_ID,
GROUP_ORDER_ERROR_ID,
MISSED_SPACING_ERROR_ID,
ORDER_ERROR_ID,
additionalCustomGroupMatchOptionsJsonSchema,
} from './sort-enums/types.js'
import { defaultOptions, sortEnum } from './sort-enums/sort-enum.js'
import { AST_NODE_TYPES } from '@typescript-eslint/utils'
var sort_enums_default = createEslintRule({
meta: {
schema: {
items: {
properties: {
...buildCommonJsonSchemas(),
...buildCommonGroupsJsonSchemas({
additionalCustomGroupMatchProperties:
additionalCustomGroupMatchOptionsJsonSchema,
}),
useConfigurationIf: buildUseConfigurationIfJsonSchema({
additionalProperties: {
matchesAstSelector: matchesAstSelectorJsonSchema,
},
}),
sortByValue: {
description: 'Specifies whether to sort enums by value.',
enum: ['always', 'ifNumericEnum', 'never'],
type: 'string',
},
useExperimentalDependencyDetection:
useExperimentalDependencyDetectionJsonSchema,
partitionByComment: partitionByCommentJsonSchema,
partitionByNewLine: partitionByNewLineJsonSchema,
},
additionalProperties: false,
type: 'object',
},
uniqueItems: true,
type: 'array',
},
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-enums',
description: 'Enforce sorted TypeScript enums.',
recommended: true,
},
type: 'suggestion',
fixable: 'code',
},
create: context =>
buildAstListeners({
nodeTypes: [AST_NODE_TYPES.TSEnumDeclaration],
sorter: sortEnum,
context,
}),
defaultOptions: [defaultOptions],
name: 'sort-enums',
})
export { sort_enums_default as default }
@@ -0,0 +1,16 @@
import { ComparatorByOptionsComputer } from '../../utils/compare/default-comparator-by-options-computer.js'
import { SortEnumsSortingNode, Options } from './types.js'
/**
* Builds a comparator computer function for sorting enum members.
*
* Creates a function that returns the appropriate comparator based on the
* sorting options and whether the enum is numeric. Handles sorting by name or
* by value depending on the `sortByValue` option.
*
* @param isNumericEnum - Whether the enum contains only numeric values.
* @returns A comparator computer function that creates comparators from
* options.
*/
export declare function buildComparatorByOptionsComputer(
isNumericEnum: boolean,
): ComparatorByOptionsComputer<Required<Options[number]>, SortEnumsSortingNode>
@@ -0,0 +1,84 @@
import { UnreachableCaseError } from '../../utils/unreachable-case-error.js'
import { buildLineLengthComparator } from '../../utils/compare/build-line-length-comparator.js'
import { compareAlphabetically } from '../../utils/compare/compare-alphabetically.js'
import { compareByCustomSort } from '../../utils/compare/compare-by-custom-sort.js'
import { unsortedComparator } from '../../utils/compare/unsorted-comparator.js'
import { compareNaturally } from '../../utils/compare/compare-naturally.js'
import { defaultComparatorByOptionsComputer } from '../../utils/compare/default-comparator-by-options-computer.js'
/**
* Builds a comparator computer function for sorting enum members.
*
* Creates a function that returns the appropriate comparator based on the
* sorting options and whether the enum is numeric. Handles sorting by name or
* by value depending on the `sortByValue` option.
*
* @param isNumericEnum - Whether the enum contains only numeric values.
* @returns A comparator computer function that creates comparators from
* options.
*/
function buildComparatorByOptionsComputer(isNumericEnum) {
return options => {
switch (options.sortByValue) {
case 'ifNumericEnum':
if (isNumericEnum) {
return byNumericValueComparatorComputer(options)
}
return defaultComparatorByOptionsComputer(options)
case 'always':
if (isNumericEnum) {
return byNumericValueComparatorComputer(options)
}
return byNonNumericValueComparatorComputer(options)
case 'never':
return defaultComparatorByOptionsComputer(options)
/* v8 ignore next 2 -- @preserve Exhaustive guard. */
default:
throw new UnreachableCaseError(options.sortByValue)
}
}
}
var byNonNumericValueComparatorComputer = options => {
switch (options.type) {
/* v8 ignore next 2 -- @preserve Untested for now as not a relevant sort for this rule. */
case 'subgroup-order':
return defaultComparatorByOptionsComputer(options)
case 'alphabetical':
return (a, b) =>
compareAlphabetically(a.value ?? '', b.value ?? '', options)
case 'line-length':
return buildLineLengthComparator(options)
case 'unsorted':
return unsortedComparator
case 'natural':
return (a, b) => compareNaturally(a.value ?? '', b.value ?? '', options)
case 'custom':
return (a, b) =>
compareByCustomSort(a.value ?? '', b.value ?? '', options)
/* v8 ignore next 2 -- @preserve Exhaustive guard. */
default:
throw new UnreachableCaseError(options.type)
}
}
var byNumericValueComparatorComputer = options => {
switch (options.type) {
/* v8 ignore next 2 -- @preserve Untested for now as not a relevant sort for this rule. */
case 'subgroup-order':
return defaultComparatorByOptionsComputer(options)
case 'alphabetical':
case 'line-length':
case 'natural':
case 'custom':
return (a, b) =>
compareNaturally(
a.numericValue.toString(),
b.numericValue.toString(),
options,
)
case 'unsorted':
return unsortedComparator
/* v8 ignore next 2 -- @preserve Exhaustive guard. */
default:
throw new UnreachableCaseError(options.type)
}
}
export { buildComparatorByOptionsComputer }
@@ -0,0 +1,13 @@
import { TSESLint } from '@typescript-eslint/utils'
import { SortEnumsSortingNode } from './types.js'
type SortingNodeWithoutDependencies = Omit<SortEnumsSortingNode, 'dependencies'>
export declare function computeDependenciesBySortingNode({
sortingNodes,
sourceCode,
enumName,
}: {
sortingNodes: SortingNodeWithoutDependencies[]
sourceCode: TSESLint.SourceCode
enumName: string
}): Map<SortingNodeWithoutDependencies, SortingNodeWithoutDependencies[]>
export {}
@@ -0,0 +1,51 @@
import { computeDependenciesBySortingNode as computeDependenciesBySortingNode$1 } from '../../utils/compute-dependencies-by-sorting-node.js'
import { computeParentNodesWithTypes } from '../../utils/compute-parent-nodes-with-types.js'
import { doesSortingNodeHaveOneOfDependencyNames } from '../../utils/does-sorting-node-have-one-of-dependency-names.js'
import { AST_NODE_TYPES } from '@typescript-eslint/utils'
function computeDependenciesBySortingNode({
sortingNodes,
sourceCode,
enumName,
}) {
return computeDependenciesBySortingNode$1({
additionalIdentifierDependenciesComputer:
buildAdditionalIdentifierDependenciesComputer({
sortingNodes,
enumName,
}),
sortingNodes,
sourceCode,
})
}
function buildAdditionalIdentifierDependenciesComputer({
sortingNodes,
enumName,
}) {
return ({ referencingSortingNode, reference }) => {
if (reference.identifier.name !== enumName) {
return []
}
let relatedIdentifiers = computeMemberExpressionIdentifiers(
reference.identifier,
referencingSortingNode,
)
return sortingNodes.filter(sortingNode =>
doesSortingNodeHaveOneOfDependencyNames(sortingNode, relatedIdentifiers),
)
}
}
function computeMemberExpressionIdentifiers(
identifier,
referencingSortingNode,
) {
return computeParentNodesWithTypes({
allowedTypes: [AST_NODE_TYPES.MemberExpression],
maxParent: referencingSortingNode.node,
consecutiveOnly: true,
node: identifier,
})
.map(node => node.property)
.filter(property => property.type === AST_NODE_TYPES.Identifier)
.map(property => property.name)
}
export { computeDependenciesBySortingNode }
@@ -0,0 +1,14 @@
import { TSESTree } from '@typescript-eslint/types'
/**
* Extract dependencies from an enum.
*
* @deprecated - To remove when experimental dependency detection is the only
* option.
* @param expression - The enum or class declaration node.
* @param enumName - The name of the enum being processed.
* @returns The list of dependencies.
*/
export declare function computeDependencies(
expression: TSESTree.Expression,
enumName: string,
): string[]
@@ -0,0 +1,85 @@
import { AST_NODE_TYPES } from '@typescript-eslint/utils'
/**
* Extract dependencies from an enum.
*
* @deprecated - To remove when experimental dependency detection is the only
* option.
* @param expression - The enum or class declaration node.
* @param enumName - The name of the enum being processed.
* @returns The list of dependencies.
*/
function computeDependencies(expression, enumName) {
let dependencies = []
let stack = [expression]
while (stack.length > 0) {
let node = stack.pop()
switch (node.type) {
case AST_NODE_TYPES.MemberExpression:
if (
node.object.type === AST_NODE_TYPES.Identifier &&
node.object.name === enumName &&
node.property.type === AST_NODE_TYPES.Identifier
) {
dependencies.push(node.property.name)
}
break
case AST_NODE_TYPES.Identifier:
dependencies.push(node.name)
break
default:
break
}
if ('alternate' in node && node.alternate) {
stack.push(node.alternate)
}
if ('argument' in node && node.argument) {
stack.push(node.argument)
}
if ('arguments' in node) {
stack.push(...node.arguments)
}
if ('consequent' in node) {
if (Array.isArray(node.consequent)) {
/* v8 ignore if -- @preserve Unsure if we can reach it. */
stack.push(...node.consequent)
} else {
stack.push(node.consequent)
}
}
if ('elements' in node) {
let elements = node.elements.filter(currentNode => currentNode !== null)
stack.push(...elements)
}
if ('expressions' in node) {
stack.push(...node.expressions)
}
if ('key' in node) {
stack.push(node.key)
}
if ('left' in node) {
stack.push(node.left)
}
if ('object' in node) {
stack.push(node.object)
}
if ('properties' in node) {
stack.push(...node.properties)
}
if ('right' in node) {
stack.push(node.right)
}
if ('test' in node && node.test) {
stack.push(node.test)
}
if (
'value' in node &&
node.value &&
typeof node.value === 'object' &&
'type' in node.value
) {
stack.push(node.value)
}
}
return dependencies
}
export { computeDependencies }
@@ -0,0 +1,13 @@
import { TSESTree } from '@typescript-eslint/types'
/**
* Extracts a numeric value from an AST expression node.
*
* Handles literal numbers, binary expressions, and unary expressions
* recursively to compute the final numeric value.
*
* @param expression - The AST node to evaluate.
* @returns The numeric value of the expression, or null if not evaluable.
*/
export declare function computeExpressionNumberValue(
expression: TSESTree.Node,
): number | null
@@ -0,0 +1,105 @@
import { AST_NODE_TYPES } from '@typescript-eslint/utils'
/**
* Extracts a numeric value from an AST expression node.
*
* Handles literal numbers, binary expressions, and unary expressions
* recursively to compute the final numeric value.
*
* @param expression - The AST node to evaluate.
* @returns The numeric value of the expression, or null if not evaluable.
*/
function computeExpressionNumberValue(expression) {
switch (expression.type) {
case AST_NODE_TYPES.BinaryExpression:
return computeBinaryExpressionNumberValue(
expression.left,
expression.right,
expression.operator,
)
case AST_NODE_TYPES.UnaryExpression:
return computeUnaryExpressionNumberValue(
expression.argument,
expression.operator,
)
case AST_NODE_TYPES.Literal:
return typeof expression.value === 'number' ? expression.value : null
default:
return null
}
}
/**
* Evaluates a binary expression to compute its numeric value.
*
* Supports arithmetic operators (+, -, *, /, %, **), bitwise operators (|, &,
* ^, <<, >>), and returns null for unsupported expressions.
*
* @param leftExpression - The left operand of the binary expression.
* @param rightExpression - The right operand of the binary expression.
* @param operator - The operator string (e.g., '+', '-', '*', '|').
* @returns The computed numeric value, or null if it cannot be evaluated.
*/
function computeBinaryExpressionNumberValue(
leftExpression,
rightExpression,
operator,
) {
let left = computeExpressionNumberValue(leftExpression)
let right = computeExpressionNumberValue(rightExpression)
if (left === null || right === null) {
return null
}
switch (operator) {
case '**':
return left ** right
case '>>':
return left >> right
case '<<':
return left << right
case '+':
return left + right
case '-':
return left - right
case '*':
return left * right
case '/':
return left / right
case '%':
return left % right
case '|':
return left | right
case '&':
return left & right
case '^':
return left ^ right
/* v8 ignore next 2 -- @preserve Unsure if we can reach it. */
default:
return null
}
}
/**
* Evaluates a unary expression to compute its numeric value.
*
* Supports unary plus (+), minus (-), and bitwise NOT (~) operators.
*
* @param argumentExpression - The operand of the unary expression.
* @param operator - The operator string (e.g., '+', '-', '~').
* @returns The computed numeric value, or null if it cannot be evaluated.
*/
function computeUnaryExpressionNumberValue(argumentExpression, operator) {
let argument = computeExpressionNumberValue(argumentExpression)
if (argument === null) {
return null
}
switch (operator) {
case '+':
return argument
case '-':
return -argument
case '~':
return ~argument
/* v8 ignore next 2 -- @preserve Unsure if we can reach it. */
default:
return null
}
}
export { computeExpressionNumberValue }
@@ -0,0 +1,24 @@
import { RuleContext } from '@typescript-eslint/utils/ts-eslint'
import { TSESTree } from '@typescript-eslint/types'
import { Options } from './types.js'
/**
* Computes the matched context options for a given enum node.
*
* @param params - Parameters.
* @param params.enumMembers - The enum members of the enum declaration node.
* @param params.matchedAstSelectors - The matched AST selectors for an enum
* node.
* @param params.context - The rule context.
* @returns The matched context options or undefined if none match.
*/
export declare function computeMatchedContextOptions<
MessageIds extends string,
>({
matchedAstSelectors,
enumMembers,
context,
}: {
context: Readonly<RuleContext<MessageIds, Options>>
matchedAstSelectors: ReadonlySet<string>
enumMembers: TSESTree.TSEnumMember[]
}): Options[number] | undefined
@@ -0,0 +1,48 @@
import { passesAllNamesMatchPatternFilter } from '../../utils/context-matching/passes-all-names-match-pattern-filter.js'
import { passesAstSelectorFilter } from '../../utils/context-matching/passes-ast-selector-filter.js'
import { computeNodeName } from './compute-node-name.js'
/**
* Computes the matched context options for a given enum node.
*
* @param params - Parameters.
* @param params.enumMembers - The enum members of the enum declaration node.
* @param params.matchedAstSelectors - The matched AST selectors for an enum
* node.
* @param params.context - The rule context.
* @returns The matched context options or undefined if none match.
*/
function computeMatchedContextOptions({
matchedAstSelectors,
enumMembers,
context,
}) {
let nodeNames = enumMembers.map(enumMember =>
computeNodeName({
sourceCode: context.sourceCode,
node: enumMember,
}),
)
return context.options.find(options =>
isContextOptionMatching({
matchedAstSelectors,
nodeNames,
options,
}),
)
}
function isContextOptionMatching({ matchedAstSelectors, nodeNames, options }) {
if (!options.useConfigurationIf) {
return true
}
return (
passesAllNamesMatchPatternFilter({
allNamesMatchPattern: options.useConfigurationIf.allNamesMatchPattern,
nodeNames,
}) &&
passesAstSelectorFilter({
matchesAstSelector: options.useConfigurationIf.matchesAstSelector,
matchedAstSelectors,
})
)
}
export { computeMatchedContextOptions }
@@ -0,0 +1,17 @@
import { TSESTree } from '@typescript-eslint/types'
import { TSESLint } from '@typescript-eslint/utils'
/**
* Computes the name of an enum member node.
*
* @param params - Parameters for group-based sorting.
* @param params.node - The enum member node.
* @param params.sourceCode - The source code object.
* @returns The computed name of the enum member node.
*/
export declare function computeNodeName({
sourceCode,
node,
}: {
sourceCode: TSESLint.SourceCode
node: TSESTree.TSEnumMember
}): string
@@ -0,0 +1,15 @@
import { AST_NODE_TYPES } from '@typescript-eslint/utils'
/**
* Computes the name of an enum member node.
*
* @param params - Parameters for group-based sorting.
* @param params.node - The enum member node.
* @param params.sourceCode - The source code object.
* @returns The computed name of the enum member node.
*/
function computeNodeName({ sourceCode, node }) {
return node.id.type === AST_NODE_TYPES.Literal ?
node.id.value
: sourceCode.getText(node.id)
}
export { computeNodeName }
@@ -0,0 +1,13 @@
import { RuleContext } from '@typescript-eslint/utils/ts-eslint'
import { TSESTree } from '@typescript-eslint/types'
import { MessageId, Options } from './types.js'
export declare let defaultOptions: Required<Options[number]>
export declare function sortEnum({
matchedAstSelectors,
context,
node,
}: {
context: Readonly<RuleContext<MessageId, Options>>
matchedAstSelectors: ReadonlySet<string>
node: TSESTree.TSEnumDeclaration
}): void
@@ -0,0 +1,179 @@
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 { 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 { isSortable } from '../../utils/is-sortable.js'
import { complete } from '../../utils/complete.js'
import { getEnumMembers } from '../../utils/get-enum-members.js'
import {
DEPENDENCY_ORDER_ERROR_ID,
EXTRA_SPACING_ERROR_ID,
GROUP_ORDER_ERROR_ID,
MISSED_SPACING_ERROR_ID,
ORDER_ERROR_ID,
} from './types.js'
import { computeDependenciesBySortingNode } from './compute-dependencies-by-sorting-node.js'
import { buildComparatorByOptionsComputer } from './build-comparator-by-options-computer.js'
import { computeExpressionNumberValue } from './compute-expression-number-value.js'
import { computeNodeName } from './compute-node-name.js'
import { computeMatchedContextOptions } from './compute-matched-context-options.js'
import { computeDependencies } from './compute-dependencies.js'
import { AST_NODE_TYPES } from '@typescript-eslint/utils'
var defaultOptions = {
useExperimentalDependencyDetection: true,
fallbackSort: { type: 'unsorted' },
newlinesInside: 'newlinesBetween',
sortByValue: 'ifNumericEnum',
partitionByComment: false,
partitionByNewLine: false,
specialCharacters: 'keep',
newlinesBetween: 'ignore',
useConfigurationIf: {},
type: 'alphabetical',
ignoreCase: true,
locales: 'en-US',
customGroups: [],
alphabet: '',
order: 'asc',
groups: [],
}
function sortEnum({ matchedAstSelectors, context, node }) {
let members = getEnumMembers(node)
if (
!isSortable(members) ||
!members.every(({ initializer }) => initializer)
) {
return
}
let settings = getSettings(context.settings)
let options = complete(
computeMatchedContextOptions({
enumMembers: members,
matchedAstSelectors,
context,
}),
settings,
defaultOptions,
)
validateCustomSortConfiguration(options)
validateGroupsConfiguration({
selectors: [],
modifiers: [],
options,
})
validateNewlinesAndPartitionConfiguration(options)
let { sourceCode, id } = context
let eslintDisabledLines = getEslintDisabledLines({
ruleName: id,
sourceCode,
})
let optionsByGroupIndexComputer = buildOptionsByGroupIndexComputer(options)
let sortingNodeGroups = members.reduce(
(accumulator, member) => {
let name = computeNodeName({
node: member,
sourceCode,
})
let group = computeGroup({
customGroupMatcher: customGroup =>
doesCustomGroupMatch({
elementValue: sourceCode.getText(member.initializer),
elementName: name,
selectors: [],
modifiers: [],
customGroup,
}),
predefinedGroups: [],
options,
})
let lastSortingNode = accumulator.at(-1)?.at(-1)
let sortingNode = {
dependencies:
options.useExperimentalDependencyDetection ?
[]
: computeDependencies(member.initializer, node.id.name),
value:
member.initializer?.type === AST_NODE_TYPES.Literal ?
(member.initializer.value?.toString() ?? null)
: null,
isEslintDisabled: isNodeEslintDisabled(member, eslintDisabledLines),
numericValue: computeExpressionNumberValue(member.initializer),
size: rangeToDiff(member, sourceCode),
dependencyNames: [name],
node: member,
group,
name,
}
if (
shouldPartition({
lastSortingNode,
sortingNode,
sourceCode,
options,
})
) {
accumulator.push([])
}
accumulator.at(-1).push({
...sortingNode,
partitionId: accumulator.length,
})
return accumulator
},
[[]],
)
if (options.useExperimentalDependencyDetection) {
sortingNodeGroups = populateSortingNodeGroupsWithDependencies({
dependenciesBySortingNode: computeDependenciesBySortingNode({
sortingNodes: sortingNodeGroups.flat(),
enumName: node.id.name,
sourceCode,
}),
sortingNodeGroups,
})
}
let sortingNodes = sortingNodeGroups.flat()
let isNumericEnum = sortingNodes.every(
sortingNode => sortingNode.numericValue !== null,
)
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,
},
sortNodesExcludingEslintDisabled,
nodes: sortingNodes,
options,
context,
})
function sortNodesExcludingEslintDisabled(ignoreEslintDisabledNodes) {
return sortNodesByDependencies(
sortingNodeGroups.flatMap(sortingNodeGroup =>
sortNodesByGroups({
comparatorByOptionsComputer:
buildComparatorByOptionsComputer(isNumericEnum),
optionsByGroupIndexComputer,
ignoreEslintDisabledNodes,
nodes: sortingNodeGroup,
groups: options.groups,
}),
),
{ ignoreEslintDisabledNodes },
)
}
}
export { defaultOptions, sortEnum }
@@ -0,0 +1,82 @@
import { JSONSchema4 } from '@typescript-eslint/utils/json-schema'
import { TSESTree } from '@typescript-eslint/types'
import { SortingNodeWithDependencies } from '../../utils/sort-nodes-by-dependencies.js'
import { RegexOption, TypeOption } from '../../types/common-options.js'
import { AllCommonOptions } from '../../types/all-common-options.js'
export type MessageId =
| typeof DEPENDENCY_ORDER_ERROR_ID
| typeof MISSED_SPACING_ERROR_ID
| typeof EXTRA_SPACING_ERROR_ID
| typeof GROUP_ORDER_ERROR_ID
| typeof ORDER_ERROR_ID
export declare const ORDER_ERROR_ID = 'unexpectedEnumsOrder'
export declare const GROUP_ORDER_ERROR_ID = 'unexpectedEnumsGroupOrder'
export declare const EXTRA_SPACING_ERROR_ID = 'extraSpacingBetweenEnumsMembers'
export declare const MISSED_SPACING_ERROR_ID =
'missedSpacingBetweenEnumsMembers'
export declare const DEPENDENCY_ORDER_ERROR_ID =
'unexpectedEnumsDependencyOrder'
/**
* Configuration options for the sort-enums rule.
*
* This rule enforces consistent ordering of TypeScript enum members to improve
* code readability and maintainability.
*/
export type Options = Partial<
{
/**
* Conditional configuration based on pattern matching.
*/
useConfigurationIf: {
/**
* Regular expression pattern to match against all enum element names.
* The rule is only applied when all names match this pattern.
*/
allNamesMatchPattern?: RegexOption
/**
* AST selector to match against TSEnumDeclaration nodes.
*/
matchesAstSelector?: string
}
/**
* Whether to sort enum members by their values instead of names. When
* "always", compares enum values; when "never", compares enum member
* names.
*
* @default ifNumericEnum
*/
sortByValue: 'ifNumericEnum' | 'always' | 'never'
/**
* Enables experimental dependency detection.
*/
useExperimentalDependencyDetection: boolean
} & AllCommonOptions<
TypeOption,
AdditionalSortOptions,
CustomGroupMatchOptions
>
>[]
export interface SortEnumsSortingNode extends SortingNodeWithDependencies<TSESTree.TSEnumMember> {
numericValue: number | null
value: string | null
}
/**
* Match options for a custom group.
*/
interface CustomGroupMatchOptions {
/**
* Regular expression pattern to match enum member values. Members with
* values matching this pattern will be included in this custom group.
*/
elementValuePattern?: RegexOption
}
type AdditionalSortOptions = object
/**
* Additional custom group match options JSON schema. Used by ESLint to validate
* rule options at configuration time.
*/
export declare let additionalCustomGroupMatchOptionsJsonSchema: Record<
string,
JSONSchema4
>
export {}
@@ -0,0 +1,21 @@
import { buildRegexJsonSchema } from '../../utils/json-schemas/common-json-schemas.js'
var ORDER_ERROR_ID = 'unexpectedEnumsOrder'
var GROUP_ORDER_ERROR_ID = 'unexpectedEnumsGroupOrder'
var EXTRA_SPACING_ERROR_ID = 'extraSpacingBetweenEnumsMembers'
var MISSED_SPACING_ERROR_ID = 'missedSpacingBetweenEnumsMembers'
var DEPENDENCY_ORDER_ERROR_ID = 'unexpectedEnumsDependencyOrder'
/**
* Additional custom group match options JSON schema. Used by ESLint to validate
* rule options at configuration time.
*/
var additionalCustomGroupMatchOptionsJsonSchema = {
elementValuePattern: buildRegexJsonSchema(),
}
export {
DEPENDENCY_ORDER_ERROR_ID,
EXTRA_SPACING_ERROR_ID,
GROUP_ORDER_ERROR_ID,
MISSED_SPACING_ERROR_ID,
ORDER_ERROR_ID,
additionalCustomGroupMatchOptionsJsonSchema,
}
@@ -0,0 +1,20 @@
declare const ORDER_ERROR_ID = 'unexpectedExportAttributesOrder'
declare const GROUP_ORDER_ERROR_ID = 'unexpectedExportAttributesGroupOrder'
declare const EXTRA_SPACING_ERROR_ID = 'extraSpacingBetweenExportAttributes'
declare const MISSED_SPACING_ERROR_ID = 'missedSpacingBetweenExportAttributes'
type MessageId =
| typeof MISSED_SPACING_ERROR_ID
| typeof EXTRA_SPACING_ERROR_ID
| typeof GROUP_ORDER_ERROR_ID
| typeof ORDER_ERROR_ID
declare const _default: import('@typescript-eslint/utils/ts-eslint').RuleModule<
MessageId,
import('...js').SortImportAttributesOptions,
{
recommended?: boolean
},
import('@typescript-eslint/utils/ts-eslint').RuleListener
> & {
name: string
}
export default _default
@@ -0,0 +1,72 @@
import {
EXTRA_SPACING_ERROR,
GROUP_ORDER_ERROR,
MISSED_SPACING_ERROR,
ORDER_ERROR,
} from '../utils/report-errors.js'
import { buildAstListeners } from '../utils/build-ast-listeners.js'
import { createEslintRule } from '../utils/create-eslint-rule.js'
import { sortImportOrExportAttributes } from './sort-import-attributes/sort-import-or-export-attributes.js'
import { jsonSchema } from './sort-import-attributes.js'
import { AST_NODE_TYPES } from '@typescript-eslint/utils'
var ORDER_ERROR_ID = 'unexpectedExportAttributesOrder'
var GROUP_ORDER_ERROR_ID = 'unexpectedExportAttributesGroupOrder'
var EXTRA_SPACING_ERROR_ID = 'extraSpacingBetweenExportAttributes'
var MISSED_SPACING_ERROR_ID = 'missedSpacingBetweenExportAttributes'
var defaultOptions = {
fallbackSort: { type: 'unsorted' },
newlinesInside: 'newlinesBetween',
specialCharacters: 'keep',
partitionByComment: false,
partitionByNewLine: false,
newlinesBetween: 'ignore',
useConfigurationIf: {},
type: 'alphabetical',
ignoreCase: true,
customGroups: [],
locales: 'en-US',
alphabet: '',
order: 'asc',
groups: [],
}
var sort_export_attributes_default = createEslintRule({
meta: {
messages: {
[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-export-attributes',
description: 'Enforce sorted export attributes.',
recommended: true,
},
schema: jsonSchema,
type: 'suggestion',
fixable: 'code',
},
create: context =>
buildAstListeners({
nodeTypes: [AST_NODE_TYPES.ExportNamedDeclaration],
sorter: sortExportAttributes,
context,
}),
defaultOptions: [defaultOptions],
name: 'sort-export-attributes',
})
function sortExportAttributes({ matchedAstSelectors, context, node }) {
sortImportOrExportAttributes({
availableMessageIds: {
missedSpacingBetweenMembers: MISSED_SPACING_ERROR_ID,
extraSpacingBetweenMembers: EXTRA_SPACING_ERROR_ID,
unexpectedGroupOrder: GROUP_ORDER_ERROR_ID,
unexpectedOrder: ORDER_ERROR_ID,
},
matchedAstSelectors,
defaultOptions,
context,
node,
})
}
export { sort_export_attributes_default as default }
@@ -0,0 +1,2 @@
import { Options as SortImportAttributesOptions } from '../sort-import-attributes/types.js'
export type Options = SortImportAttributesOptions
@@ -0,0 +1,24 @@
import { TSESLint } from '@typescript-eslint/utils'
import { Options } from './sort-exports/types.js'
declare const ORDER_ERROR_ID = 'unexpectedExportsOrder'
declare const GROUP_ORDER_ERROR_ID = 'unexpectedExportsGroupOrder'
declare const EXTRA_SPACING_ERROR_ID = 'extraSpacingBetweenExports'
declare const MISSED_SPACING_ERROR_ID = 'missedSpacingBetweenExports'
declare const MISSED_COMMENT_ABOVE_ERROR_ID = 'missedCommentAboveExport'
type MessageId =
| typeof MISSED_COMMENT_ABOVE_ERROR_ID
| typeof MISSED_SPACING_ERROR_ID
| typeof EXTRA_SPACING_ERROR_ID
| typeof GROUP_ORDER_ERROR_ID
| typeof ORDER_ERROR_ID
declare const _default: TSESLint.RuleModule<
MessageId,
Options,
{
recommended?: boolean
},
TSESLint.RuleListener
> & {
name: string
}
export default _default
@@ -0,0 +1,236 @@
import { buildCommonJsonSchemas } from '../utils/json-schemas/common-json-schemas.js'
import { buildCommonGroupsJsonSchemas } from '../utils/json-schemas/common-groups-json-schemas.js'
import { UnreachableCaseError } from '../utils/unreachable-case-error.js'
import {
EXTRA_SPACING_ERROR,
GROUP_ORDER_ERROR,
MISSED_COMMENT_ABOVE_ERROR,
MISSED_SPACING_ERROR,
ORDER_ERROR,
} from '../utils/report-errors.js'
import {
partitionByCommentJsonSchema,
partitionByNewLineJsonSchema,
} from '../utils/json-schemas/common-partition-json-schemas.js'
import { validateNewlinesAndPartitionConfiguration } from '../utils/validate-newlines-and-partition-configuration.js'
import { defaultComparatorByOptionsComputer } from '../utils/compare/default-comparator-by-options-computer.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 { 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 { isNodeOnSingleLine } from '../utils/is-node-on-single-line.js'
import {
additionalCustomGroupMatchOptionsJsonSchema,
allModifiers,
allSelectors,
} from './sort-exports/types.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 = 'unexpectedExportsOrder'
var GROUP_ORDER_ERROR_ID = 'unexpectedExportsGroupOrder'
var EXTRA_SPACING_ERROR_ID = 'extraSpacingBetweenExports'
var MISSED_SPACING_ERROR_ID = 'missedSpacingBetweenExports'
var MISSED_COMMENT_ABOVE_ERROR_ID = 'missedCommentAboveExport'
var defaultOptions = {
fallbackSort: { type: 'unsorted' },
newlinesInside: 'newlinesBetween',
specialCharacters: 'keep',
partitionByComment: false,
newlinesBetween: 'ignore',
partitionByNewLine: false,
type: 'alphabetical',
customGroups: [],
ignoreCase: true,
locales: 'en-US',
alphabet: '',
order: 'asc',
groups: [],
}
var sort_exports_default = createEslintRule({
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,
})
let formattedMembers = [[]]
function registerNode(node) {
if (!node.source) {
return
}
let selector = 'export'
let modifiers = [
computeExportKindModifier(node),
computeExportTypeModifier(node),
computeLineCountModifier(node),
]
let name = node.source.value
let group = computeGroup({
customGroupMatcher: customGroup =>
doesCustomGroupMatch({
selectors: [selector],
elementName: name,
customGroup,
modifiers,
}),
predefinedGroups: generatePredefinedGroups({
cache: cachedGroupsByModifiersAndSelectors,
selectors: [selector],
modifiers,
}),
options,
})
let sortingNode = {
isEslintDisabled: isNodeEslintDisabled(node, eslintDisabledLines),
size: rangeToDiff(node, sourceCode),
addSafetySemicolonWhenInline: true,
group,
name,
node,
}
let lastSortingNode = formattedMembers.at(-1)?.at(-1)
if (
shouldPartition({
lastSortingNode,
sortingNode,
sourceCode,
options,
})
) {
formattedMembers.push([])
}
formattedMembers.at(-1).push({
...sortingNode,
partitionId: formattedMembers.length,
})
}
return {
'Program:exit': () => {
sortExportNodes({
formattedMembers,
context,
options,
})
},
ExportNamedDeclaration: registerNode,
ExportAllDeclaration: registerNode,
}
},
meta: {
schema: {
items: {
properties: {
...buildCommonJsonSchemas(),
...buildCommonGroupsJsonSchemas({
additionalCustomGroupMatchProperties:
additionalCustomGroupMatchOptionsJsonSchema,
}),
partitionByComment: partitionByCommentJsonSchema,
partitionByNewLine: partitionByNewLineJsonSchema,
},
additionalProperties: false,
type: 'object',
},
uniqueItems: true,
type: 'array',
},
messages: {
[MISSED_COMMENT_ABOVE_ERROR_ID]: MISSED_COMMENT_ABOVE_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-exports',
description: 'Enforce sorted exports.',
recommended: true,
},
type: 'suggestion',
fixable: 'code',
},
defaultOptions: [defaultOptions],
name: 'sort-exports',
})
function sortExportNodes({ formattedMembers, context, options }) {
let optionsByGroupIndexComputer = buildOptionsByGroupIndexComputer(options)
let nodes = formattedMembers.flat()
reportAllErrors({
availableMessageIds: {
missedSpacingBetweenMembers: MISSED_SPACING_ERROR_ID,
extraSpacingBetweenMembers: EXTRA_SPACING_ERROR_ID,
missedCommentAbove: MISSED_COMMENT_ABOVE_ERROR_ID,
unexpectedGroupOrder: GROUP_ORDER_ERROR_ID,
unexpectedOrder: ORDER_ERROR_ID,
},
sortNodesExcludingEslintDisabled,
options,
context,
nodes,
})
function sortNodesExcludingEslintDisabled(ignoreEslintDisabledNodes) {
return formattedMembers.flatMap(groupedNodes =>
sortNodesByGroups({
comparatorByOptionsComputer: defaultComparatorByOptionsComputer,
optionsByGroupIndexComputer,
ignoreEslintDisabledNodes,
groups: options.groups,
nodes: groupedNodes,
}),
)
}
}
function computeExportKindModifier(node) {
let exportKind = 'exportKind' in node ? node.exportKind : void 0
switch (exportKind) {
case void 0:
case 'value':
return 'value'
case 'type':
return 'type'
/* v8 ignore next 2 -- @preserve Exhaustive guard. */
default:
throw new UnreachableCaseError(exportKind)
}
}
function computeExportTypeModifier(node) {
switch (node.type) {
case AST_NODE_TYPES.ExportNamedDeclaration:
return 'named'
case AST_NODE_TYPES.ExportAllDeclaration:
return 'wildcard'
/* v8 ignore next 2 -- @preserve Exhaustive guard. */
default:
throw new UnreachableCaseError(node)
}
}
function computeLineCountModifier(node) {
if (isNodeOnSingleLine(node)) {
return 'singleline'
}
return 'multiline'
}
export { sort_exports_default as default }
@@ -0,0 +1,76 @@
import { JSONSchema4 } from '@typescript-eslint/utils/json-schema'
import { AllCommonOptions } from '../../types/all-common-options.js'
import { TypeOption } from '../../types/common-options.js'
/**
* Configuration options for the sort-exports rule.
*
* This rule enforces consistent ordering of re-export statements (`export ...
* From '...'`) to improve code organization and maintainability.
*/
export type Options = Partial<
AllCommonOptions<TypeOption, AdditionalSortOptions, CustomGroupMatchOptions>
>[]
/**
* Union type of available export modifiers. Distinguishes between value exports
* and type-only exports.
*/
export type Modifier = (typeof allModifiers)[number]
/**
* Type of selector for export statements. Currently represents re-export
* statements.
*/
export type Selector = (typeof allSelectors)[number]
/**
* Additional configuration for a single custom group.
*
* Custom groups allow fine-grained control over how export statements are
* grouped and sorted based on their module names, selectors, and modifiers.
*
* @example
*
* ```ts
* {
* "modifiers": ["type"],
* "selector": "export"
* }
* ```
*/
interface CustomGroupMatchOptions {
/**
* List of modifiers that exports must have to be included in this group.
* Can include 'value' for value exports or 'type' for type exports.
*/
modifiers?: Modifier[]
/**
* The selector type for this group. Currently only 'export' is supported
* for re-export statements.
*/
selector?: Selector
}
type AdditionalSortOptions = object
/**
* Complete list of available export selectors. Used for validation and JSON
* schema generation.
*/
export declare let allSelectors: readonly ['export']
/**
* Complete list of available export modifiers. Used for validation and JSON
* schema generation.
*/
export declare let allModifiers: readonly [
'value',
'type',
'named',
'wildcard',
'multiline',
'singleline',
]
/**
* Additional custom group match options JSON schema. Used by ESLint to validate
* rule options at configuration time.
*/
export declare let additionalCustomGroupMatchOptionsJsonSchema: Record<
string,
JSONSchema4
>
export {}
@@ -0,0 +1,34 @@
import {
buildCustomGroupModifiersJsonSchema,
buildCustomGroupSelectorJsonSchema,
} from '../../utils/json-schemas/common-groups-json-schemas.js'
/**
* Complete list of available export selectors. Used for validation and JSON
* schema generation.
*/
var allSelectors = ['export']
/**
* Complete list of available export modifiers. Used for validation and JSON
* schema generation.
*/
var allModifiers = [
'value',
'type',
'named',
'wildcard',
'multiline',
'singleline',
]
/**
* Additional custom group match options JSON schema. Used by ESLint to validate
* rule options at configuration time.
*/
var additionalCustomGroupMatchOptionsJsonSchema = {
modifiers: buildCustomGroupModifiersJsonSchema(allModifiers),
selector: buildCustomGroupSelectorJsonSchema(allSelectors),
}
export {
additionalCustomGroupMatchOptionsJsonSchema,
allModifiers,
allSelectors,
}
@@ -0,0 +1,12 @@
import { MessageId, Options } from './sort-heritage-clauses/types.js'
declare const _default: import('@typescript-eslint/utils/ts-eslint').RuleModule<
MessageId,
Options,
{
recommended?: boolean
},
import('@typescript-eslint/utils/ts-eslint').RuleListener
> & {
name: string
}
export default _default
@@ -0,0 +1,77 @@
import {
buildCommonJsonSchemas,
buildUseConfigurationIfJsonSchema,
matchesAstSelectorJsonSchema,
} from '../utils/json-schemas/common-json-schemas.js'
import { buildCommonGroupsJsonSchemas } from '../utils/json-schemas/common-groups-json-schemas.js'
import {
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 { buildAstListeners } from '../utils/build-ast-listeners.js'
import { createEslintRule } from '../utils/create-eslint-rule.js'
import {
EXTRA_SPACING_ERROR_ID,
GROUP_ORDER_ERROR_ID,
MISSED_SPACING_ERROR_ID,
ORDER_ERROR_ID,
} from './sort-heritage-clauses/types.js'
import {
defaultOptions,
sortHeritageClause,
} from './sort-heritage-clauses/sort-heritage-clause.js'
import { AST_NODE_TYPES } from '@typescript-eslint/utils'
var sort_heritage_clauses_default = createEslintRule({
meta: {
schema: {
items: {
properties: {
...buildCommonJsonSchemas(),
...buildCommonGroupsJsonSchemas(),
useConfigurationIf: buildUseConfigurationIfJsonSchema({
additionalProperties: {
matchesAstSelector: matchesAstSelectorJsonSchema,
},
}),
partitionByNewLine: partitionByNewLineJsonSchema,
partitionByComment: partitionByCommentJsonSchema,
},
additionalProperties: false,
type: 'object',
},
uniqueItems: true,
type: 'array',
},
messages: {
[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-heritage-clauses',
description: 'Enforce sorted heritage clauses.',
recommended: true,
},
type: 'suggestion',
fixable: 'code',
},
create: context =>
buildAstListeners({
nodeTypes: [
AST_NODE_TYPES.TSInterfaceDeclaration,
AST_NODE_TYPES.ClassDeclaration,
],
sorter: sortHeritageClause,
context,
}),
defaultOptions: [defaultOptions],
name: 'sort-heritage-clauses',
})
export { sort_heritage_clauses_default as default }
@@ -0,0 +1,24 @@
import { RuleContext } from '@typescript-eslint/utils/ts-eslint'
import { TSESTree } from '@typescript-eslint/types'
import { Options } from './types.js'
/**
* Computes the matched context options for a given heritage clause parent node.
*
* @param params - Parameters.
* @param params.heritageClauses - The heritage clauses of the parent node.
* @param params.matchedAstSelectors - The matched AST selectors for an object
* node.
* @param params.context - The rule context.
* @returns The matched context options or undefined if none match.
*/
export declare function computeMatchedContextOptions<
MessageIds extends string,
>({
matchedAstSelectors,
heritageClauses,
context,
}: {
heritageClauses: TSESTree.TSInterfaceHeritage[] | TSESTree.TSClassImplements[]
context: Readonly<RuleContext<MessageIds, Options>>
matchedAstSelectors: ReadonlySet<string>
}): Options[number] | undefined
@@ -0,0 +1,45 @@
import { passesAllNamesMatchPatternFilter } from '../../utils/context-matching/passes-all-names-match-pattern-filter.js'
import { passesAstSelectorFilter } from '../../utils/context-matching/passes-ast-selector-filter.js'
import { computeNodeName } from './compute-node-name.js'
/**
* Computes the matched context options for a given heritage clause parent node.
*
* @param params - Parameters.
* @param params.heritageClauses - The heritage clauses of the parent node.
* @param params.matchedAstSelectors - The matched AST selectors for an object
* node.
* @param params.context - The rule context.
* @returns The matched context options or undefined if none match.
*/
function computeMatchedContextOptions({
matchedAstSelectors,
heritageClauses,
context,
}) {
let nodeNames = heritageClauses.map(clause =>
computeNodeName(clause.expression),
)
return context.options.find(options =>
isContextOptionMatching({
matchedAstSelectors,
nodeNames,
options,
}),
)
}
function isContextOptionMatching({ matchedAstSelectors, nodeNames, options }) {
if (!options.useConfigurationIf) {
return true
}
return (
passesAllNamesMatchPatternFilter({
allNamesMatchPattern: options.useConfigurationIf.allNamesMatchPattern,
nodeNames,
}) &&
passesAstSelectorFilter({
matchesAstSelector: options.useConfigurationIf.matchesAstSelector,
matchedAstSelectors,
})
)
}
export { computeMatchedContextOptions }
@@ -0,0 +1,13 @@
import { TSESTree } from '@typescript-eslint/types'
/**
* Recursively extracts the name from a heritage clause expression.
*
* For simple identifiers, returns the name directly. For member expressions
* (like `Namespace.Class`), recursively extracts the property name.
*
* @param expression - The heritage clause expression AST node.
* @returns The extracted name string from the expression.
*/
export declare function computeNodeName(
expression: TSESTree.PrivateIdentifier | TSESTree.Expression,
): string
@@ -0,0 +1,24 @@
import { AST_NODE_TYPES } from '@typescript-eslint/utils'
/**
* Recursively extracts the name from a heritage clause expression.
*
* For simple identifiers, returns the name directly. For member expressions
* (like `Namespace.Class`), recursively extracts the property name.
*
* @param expression - The heritage clause expression AST node.
* @returns The extracted name string from the expression.
*/
function computeNodeName(expression) {
if (expression.type === AST_NODE_TYPES.Identifier) {
return expression.name
}
/* v8 ignore else -- @preserve Exhaustive guard for unsupported expressions. */
if ('property' in expression) {
return computeNodeName(expression.property)
}
/* v8 ignore next -- @preserve Should never throw. */
throw new Error(
'Unexpected heritage clause expression. Please report this issue here: https://github.com/azat-io/eslint-plugin-perfectionist/issues',
)
}
export { computeNodeName }
@@ -0,0 +1,13 @@
import { TSESLint } from '@typescript-eslint/utils'
import { TSESTree } from '@typescript-eslint/types'
import { MessageId, Options } from './types.js'
export declare let defaultOptions: Required<Options[number]>
export declare function sortHeritageClause({
matchedAstSelectors,
context,
node,
}: {
node: TSESTree.TSInterfaceDeclaration | TSESTree.ClassDeclaration
context: TSESLint.RuleContext<MessageId, Options>
matchedAstSelectors: ReadonlySet<string>
}): void
@@ -0,0 +1,138 @@
import { validateNewlinesAndPartitionConfiguration } from '../../utils/validate-newlines-and-partition-configuration.js'
import { defaultComparatorByOptionsComputer } from '../../utils/compare/default-comparator-by-options-computer.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 { 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 { isSortable } from '../../utils/is-sortable.js'
import { complete } from '../../utils/complete.js'
import {
EXTRA_SPACING_ERROR_ID,
GROUP_ORDER_ERROR_ID,
MISSED_SPACING_ERROR_ID,
ORDER_ERROR_ID,
} from './types.js'
import { computeNodeName } from './compute-node-name.js'
import { computeMatchedContextOptions } from './compute-matched-context-options.js'
import { AST_NODE_TYPES } from '@typescript-eslint/utils'
var defaultOptions = {
fallbackSort: { type: 'unsorted' },
newlinesInside: 'newlinesBetween',
specialCharacters: 'keep',
newlinesBetween: 'ignore',
partitionByNewLine: false,
partitionByComment: false,
useConfigurationIf: {},
type: 'alphabetical',
ignoreCase: true,
customGroups: [],
locales: 'en-US',
alphabet: '',
order: 'asc',
groups: [],
}
function sortHeritageClause({ matchedAstSelectors, context, node }) {
let heritageClauses =
node.type === AST_NODE_TYPES.TSInterfaceDeclaration ?
node.extends
: node.implements
if (!isSortable(heritageClauses)) {
return
}
let settings = getSettings(context.settings)
let options = complete(
computeMatchedContextOptions({
matchedAstSelectors,
heritageClauses,
context,
}),
settings,
defaultOptions,
)
validateCustomSortConfiguration(options)
validateGroupsConfiguration({
modifiers: [],
selectors: [],
options,
})
validateNewlinesAndPartitionConfiguration(options)
let { sourceCode, id } = context
let eslintDisabledLines = getEslintDisabledLines({
ruleName: id,
sourceCode,
})
let optionsByGroupIndexComputer = buildOptionsByGroupIndexComputer(options)
let formattedMembers = [[]]
for (let heritageClause of heritageClauses) {
let name = computeNodeName(heritageClause.expression)
let group = computeGroup({
customGroupMatcher: customGroup =>
doesCustomGroupMatch({
elementName: name,
selectors: [],
modifiers: [],
customGroup,
}),
predefinedGroups: [],
options,
})
let sortingNode = {
isEslintDisabled: isNodeEslintDisabled(
heritageClause,
eslintDisabledLines,
),
size: rangeToDiff(heritageClause, sourceCode),
node: heritageClause,
partitionId: 0,
group,
name,
}
let lastSortingNode = formattedMembers.at(-1)?.at(-1)
if (
shouldPartition({
lastSortingNode,
sortingNode,
sourceCode,
options,
})
) {
formattedMembers.push([])
}
formattedMembers.at(-1).push(sortingNode)
}
for (let nodes of formattedMembers) {
function createSortNodesExcludingEslintDisabled(sortingNodes) {
return function (ignoreEslintDisabledNodes) {
return sortNodesByGroups({
comparatorByOptionsComputer: defaultComparatorByOptionsComputer,
optionsByGroupIndexComputer,
ignoreEslintDisabledNodes,
groups: options.groups,
nodes: sortingNodes,
})
}
}
reportAllErrors({
availableMessageIds: {
missedSpacingBetweenMembers: MISSED_SPACING_ERROR_ID,
extraSpacingBetweenMembers: EXTRA_SPACING_ERROR_ID,
unexpectedGroupOrder: GROUP_ORDER_ERROR_ID,
unexpectedOrder: ORDER_ERROR_ID,
},
sortNodesExcludingEslintDisabled:
createSortNodesExcludingEslintDisabled(nodes),
options,
context,
nodes,
})
}
}
export { defaultOptions, sortHeritageClause }
@@ -0,0 +1,43 @@
import { RegexOption, TypeOption } from '../../types/common-options.js'
import { AllCommonOptions } from '../../types/all-common-options.js'
export type MessageId =
| typeof MISSED_SPACING_ERROR_ID
| typeof EXTRA_SPACING_ERROR_ID
| typeof GROUP_ORDER_ERROR_ID
| typeof ORDER_ERROR_ID
export declare const ORDER_ERROR_ID = 'unexpectedHeritageClausesOrder'
export declare const GROUP_ORDER_ERROR_ID =
'unexpectedHeritageClausesGroupOrder'
export declare const EXTRA_SPACING_ERROR_ID =
'extraSpacingBetweenHeritageClauses'
export declare const MISSED_SPACING_ERROR_ID =
'missedSpacingBetweenHeritageClauses'
export type Options = Partial<
{
/**
* Conditional configuration based on pattern matching.
*/
useConfigurationIf: {
/**
* Regular expression pattern to match against all heritage clause
* names. The rule is only applied when all names match this pattern.
*/
allNamesMatchPattern?: RegexOption
/**
* AST selector to match against ClassDeclaration or
* TSInterfaceDeclaration nodes.
*/
matchesAstSelector?: string
}
} & AllCommonOptions<
TypeOption,
AdditionalSortOptions,
CustomGroupMatchOptions
>
>[]
/**
* Match options for a custom group.
*/
type CustomGroupMatchOptions = object
type AdditionalSortOptions = object
export {}
@@ -0,0 +1,10 @@
var ORDER_ERROR_ID = 'unexpectedHeritageClausesOrder'
var GROUP_ORDER_ERROR_ID = 'unexpectedHeritageClausesGroupOrder'
var EXTRA_SPACING_ERROR_ID = 'extraSpacingBetweenHeritageClauses'
var MISSED_SPACING_ERROR_ID = 'missedSpacingBetweenHeritageClauses'
export {
EXTRA_SPACING_ERROR_ID,
GROUP_ORDER_ERROR_ID,
MISSED_SPACING_ERROR_ID,
ORDER_ERROR_ID,
}
@@ -0,0 +1,23 @@
import { JSONSchema4 } from '@typescript-eslint/utils/json-schema'
import { Options } from './sort-import-attributes/types.js'
declare const ORDER_ERROR_ID = 'unexpectedImportAttributesOrder'
declare const GROUP_ORDER_ERROR_ID = 'unexpectedImportAttributesGroupOrder'
declare const EXTRA_SPACING_ERROR_ID = 'extraSpacingBetweenImportAttributes'
declare const MISSED_SPACING_ERROR_ID = 'missedSpacingBetweenImportAttributes'
type MessageId =
| typeof MISSED_SPACING_ERROR_ID
| typeof EXTRA_SPACING_ERROR_ID
| typeof GROUP_ORDER_ERROR_ID
| typeof ORDER_ERROR_ID
export declare let jsonSchema: JSONSchema4
declare const _default: import('@typescript-eslint/utils/ts-eslint').RuleModule<
MessageId,
Options,
{
recommended?: boolean
},
import('@typescript-eslint/utils/ts-eslint').RuleListener
> & {
name: string
}
export default _default
@@ -0,0 +1,100 @@
import {
buildCommonJsonSchemas,
buildUseConfigurationIfJsonSchema,
matchesAstSelectorJsonSchema,
} from '../utils/json-schemas/common-json-schemas.js'
import { buildCommonGroupsJsonSchemas } from '../utils/json-schemas/common-groups-json-schemas.js'
import {
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 { buildAstListeners } from '../utils/build-ast-listeners.js'
import { createEslintRule } from '../utils/create-eslint-rule.js'
import { sortImportOrExportAttributes } from './sort-import-attributes/sort-import-or-export-attributes.js'
import { AST_NODE_TYPES } from '@typescript-eslint/utils'
var ORDER_ERROR_ID = 'unexpectedImportAttributesOrder'
var GROUP_ORDER_ERROR_ID = 'unexpectedImportAttributesGroupOrder'
var EXTRA_SPACING_ERROR_ID = 'extraSpacingBetweenImportAttributes'
var MISSED_SPACING_ERROR_ID = 'missedSpacingBetweenImportAttributes'
var defaultOptions = {
fallbackSort: { type: 'unsorted' },
newlinesInside: 'newlinesBetween',
specialCharacters: 'keep',
partitionByComment: false,
partitionByNewLine: false,
newlinesBetween: 'ignore',
useConfigurationIf: {},
type: 'alphabetical',
ignoreCase: true,
customGroups: [],
locales: 'en-US',
alphabet: '',
order: 'asc',
groups: [],
}
var jsonSchema = {
items: {
properties: {
...buildCommonJsonSchemas(),
...buildCommonGroupsJsonSchemas(),
useConfigurationIf: buildUseConfigurationIfJsonSchema({
additionalProperties: {
matchesAstSelector: matchesAstSelectorJsonSchema,
},
}),
partitionByComment: partitionByCommentJsonSchema,
partitionByNewLine: partitionByNewLineJsonSchema,
},
additionalProperties: false,
type: 'object',
},
uniqueItems: true,
type: 'array',
}
var sort_import_attributes_default = createEslintRule({
meta: {
messages: {
[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-import-attributes',
description: 'Enforce sorted import attributes.',
recommended: true,
},
schema: jsonSchema,
type: 'suggestion',
fixable: 'code',
},
create: context =>
buildAstListeners({
nodeTypes: [AST_NODE_TYPES.ImportDeclaration],
sorter: sortImportAttributes,
context,
}),
defaultOptions: [defaultOptions],
name: 'sort-import-attributes',
})
function sortImportAttributes({ matchedAstSelectors, context, node }) {
sortImportOrExportAttributes({
availableMessageIds: {
missedSpacingBetweenMembers: MISSED_SPACING_ERROR_ID,
extraSpacingBetweenMembers: EXTRA_SPACING_ERROR_ID,
unexpectedGroupOrder: GROUP_ORDER_ERROR_ID,
unexpectedOrder: ORDER_ERROR_ID,
},
matchedAstSelectors,
defaultOptions,
context,
node,
})
}
export { sort_import_attributes_default as default, jsonSchema }
@@ -0,0 +1,26 @@
import { RuleContext } from '@typescript-eslint/utils/ts-eslint'
import { TSESTree } from '@typescript-eslint/types'
import { Options } from './types.js'
/**
* Computes the matched context options for a given import/export attributes
* node.
*
* @param params - Parameters.
* @param params.matchedAstSelectors - The matched AST selectors for an
* import/export declaration node.
* @param params.attributes - The import attributes to compute the context
* options for.
* @param params.context - The rule context.
* @returns The matched context options or undefined if none match.
*/
export declare function computeMatchedContextOptions<
MessageIds extends string,
>({
matchedAstSelectors,
attributes,
context,
}: {
context: Readonly<RuleContext<MessageIds, Options>>
matchedAstSelectors: ReadonlySet<string>
attributes: TSESTree.ImportAttribute[]
}): Options[number] | undefined
@@ -0,0 +1,47 @@
import { passesAllNamesMatchPatternFilter } from '../../utils/context-matching/passes-all-names-match-pattern-filter.js'
import { passesAstSelectorFilter } from '../../utils/context-matching/passes-ast-selector-filter.js'
import { computeNodeName } from './compute-node-name.js'
/**
* Computes the matched context options for a given import/export attributes
* node.
*
* @param params - Parameters.
* @param params.matchedAstSelectors - The matched AST selectors for an
* import/export declaration node.
* @param params.attributes - The import attributes to compute the context
* options for.
* @param params.context - The rule context.
* @returns The matched context options or undefined if none match.
*/
function computeMatchedContextOptions({
matchedAstSelectors,
attributes,
context,
}) {
let nodeNames = attributes.map(attribute =>
computeNodeName(attribute, context.sourceCode),
)
return context.options.find(options =>
isContextOptionMatching({
matchedAstSelectors,
nodeNames,
options,
}),
)
}
function isContextOptionMatching({ matchedAstSelectors, nodeNames, options }) {
if (!options.useConfigurationIf) {
return true
}
return (
passesAllNamesMatchPatternFilter({
allNamesMatchPattern: options.useConfigurationIf.allNamesMatchPattern,
nodeNames,
}) &&
passesAstSelectorFilter({
matchesAstSelector: options.useConfigurationIf.matchesAstSelector,
matchedAstSelectors,
})
)
}
export { computeMatchedContextOptions }
@@ -0,0 +1,16 @@
import { TSESLint } from '@typescript-eslint/utils'
import { TSESTree } from '@typescript-eslint/types'
/**
* Extracts the name of an import attribute for sorting purposes.
*
* For identifier keys, returns the identifier name. For literal keys, returns
* the string value. Falls back to source code text if needed.
*
* @param attribute - The import attribute AST node.
* @param sourceCode - The ESLint source code object.
* @returns The attribute name to use for sorting.
*/
export declare function computeNodeName(
attribute: TSESTree.ImportAttribute,
sourceCode: TSESLint.SourceCode,
): string
@@ -0,0 +1,25 @@
import { UnreachableCaseError } from '../../utils/unreachable-case-error.js'
import { AST_NODE_TYPES } from '@typescript-eslint/utils'
/**
* Extracts the name of an import attribute for sorting purposes.
*
* For identifier keys, returns the identifier name. For literal keys, returns
* the string value. Falls back to source code text if needed.
*
* @param attribute - The import attribute AST node.
* @param sourceCode - The ESLint source code object.
* @returns The attribute name to use for sorting.
*/
function computeNodeName(attribute, sourceCode) {
let { key } = attribute
switch (key.type) {
case AST_NODE_TYPES.Identifier:
return key.name
case AST_NODE_TYPES.Literal:
return key.value?.toString() ?? sourceCode.getText(attribute)
/* v8 ignore next 2 -- @preserve Exhaustive guard. */
default:
throw new UnreachableCaseError(key)
}
}
export { computeNodeName }
@@ -0,0 +1,23 @@
import { TSESTree } from '@typescript-eslint/types'
import { TSESLint } from '@typescript-eslint/utils'
import { Options } from './types.js'
export declare function sortImportOrExportAttributes<
MessageIds extends string,
>({
matchedAstSelectors,
availableMessageIds,
defaultOptions,
context,
node,
}: {
availableMessageIds: {
missedSpacingBetweenMembers: MessageIds
extraSpacingBetweenMembers: MessageIds
unexpectedGroupOrder: MessageIds
unexpectedOrder: MessageIds
}
node: TSESTree.ExportNamedDeclaration | TSESTree.ImportDeclaration
context: TSESLint.RuleContext<MessageIds, Options>
defaultOptions: Required<Options[number]>
matchedAstSelectors: ReadonlySet<string>
}): void
@@ -0,0 +1,112 @@
import { validateNewlinesAndPartitionConfiguration } from '../../utils/validate-newlines-and-partition-configuration.js'
import { defaultComparatorByOptionsComputer } from '../../utils/compare/default-comparator-by-options-computer.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 { 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 { isSortable } from '../../utils/is-sortable.js'
import { complete } from '../../utils/complete.js'
import { computeNodeName } from './compute-node-name.js'
import { computeMatchedContextOptions } from './compute-matched-context-options.js'
function sortImportOrExportAttributes({
matchedAstSelectors,
availableMessageIds,
defaultOptions,
context,
node,
}) {
let attributes = node.attributes
if (!isSortable(attributes)) {
return
}
let { sourceCode, id } = context
let settings = getSettings(context.settings)
let options = complete(
computeMatchedContextOptions({
matchedAstSelectors,
attributes,
context,
}),
settings,
defaultOptions,
)
validateCustomSortConfiguration(options)
validateGroupsConfiguration({
selectors: [],
modifiers: [],
options,
})
validateNewlinesAndPartitionConfiguration(options)
let eslintDisabledLines = getEslintDisabledLines({
ruleName: id,
sourceCode,
})
let optionsByGroupIndexComputer = buildOptionsByGroupIndexComputer(options)
let formattedMembers = [[]]
for (let attribute of attributes) {
let name = computeNodeName(attribute, sourceCode)
let group = computeGroup({
customGroupMatcher: customGroup =>
doesCustomGroupMatch({
elementName: name,
selectors: [],
modifiers: [],
customGroup,
}),
predefinedGroups: [],
options,
})
let sortingNode = {
isEslintDisabled: isNodeEslintDisabled(attribute, eslintDisabledLines),
size: rangeToDiff(attribute, sourceCode),
node: attribute,
group,
name,
}
let lastSortingNode = formattedMembers.at(-1)?.at(-1)
if (
shouldPartition({
lastSortingNode,
sortingNode,
sourceCode,
options,
})
) {
formattedMembers.push([])
}
formattedMembers.at(-1).push({
...sortingNode,
partitionId: formattedMembers.length,
})
}
for (let nodes of formattedMembers) {
function createSortNodesExcludingEslintDisabled(sortingNodes) {
return function (ignoreEslintDisabledNodes) {
return sortNodesByGroups({
comparatorByOptionsComputer: defaultComparatorByOptionsComputer,
optionsByGroupIndexComputer,
ignoreEslintDisabledNodes,
groups: options.groups,
nodes: sortingNodes,
})
}
}
reportAllErrors({
sortNodesExcludingEslintDisabled:
createSortNodesExcludingEslintDisabled(nodes),
availableMessageIds,
options,
context,
nodes,
})
}
}
export { sortImportOrExportAttributes }
@@ -0,0 +1,35 @@
import { TSESTree } from '@typescript-eslint/types'
import { RegexOption, TypeOption } from '../../types/common-options.js'
import { AllCommonOptions } from '../../types/all-common-options.js'
import { SortingNode } from '../../types/sorting-node.js'
export type Options = Partial<
{
/**
* Conditional configuration based on pattern matching.
*/
useConfigurationIf: {
/**
* Regular expression pattern to match against all attribute key names.
* The rule is only applied when all names match this pattern.
*/
allNamesMatchPattern?: RegexOption
/**
* AST selector to match against ImportDeclaration or
* ExportNamedDeclaration nodes.
*/
matchesAstSelector?: string
}
} & AllCommonOptions<
TypeOption,
AdditionalSortOptions,
CustomGroupMatchOptions
>
>[]
export type SortImportAttributesSortingNode =
SortingNode<TSESTree.ImportAttribute>
/**
* Match options for a custom group.
*/
type CustomGroupMatchOptions = object
type AdditionalSortOptions = object
export {}

Some files were not shown because too many files have changed in this diff Show More