New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

eslint-plugin-perfectionist

Package Overview
Dependencies
Maintainers
1
Versions
70
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

eslint-plugin-perfectionist - npm Package Compare versions

Comparing version 3.9.1 to 4.0.0

dist/rules/sort-decorators.js

715

dist/index.d.ts

@@ -1,694 +0,41 @@

import { ClassicConfig } from '@typescript-eslint/utils/ts-eslint'
import { FlatConfig } from '@typescript-eslint/utils/ts-eslint'
import { RuleListener } from '@typescript-eslint/utils/ts-eslint'
import { RuleModule } from '@typescript-eslint/utils/ts-eslint'
import { Linter } from 'eslint'
import { Rule } from 'eslint'
declare type AbstractModifier = 'abstract'
declare const _default: PluginConfig
declare type AccessorPropertyGroup =
`${PublicOrProtectedOrPrivateModifierPrefix}${StaticOrAbstractModifierPrefix}${OverrideModifierPrefix}${DecoratedModifierPrefix}${AccessorPropertySelector}`
declare type AccessorPropertySelector = 'accessor-property'
declare type AdvancedSingleCustomGroup<T extends Selector> = {
decoratorNamePattern?: string
elementValuePattern?: string
elementNamePattern?: string
} & BaseSingleCustomGroup<T>
/**
* Only used in code as well
*/
declare interface AllowedModifiersPerSelector {
property:
| PublicOrProtectedOrPrivateModifier
| DecoratedModifier
| AbstractModifier
| OverrideModifier
| ReadonlyModifier
| OptionalModifier
| DeclareModifier
| StaticModifier
method:
| PublicOrProtectedOrPrivateModifier
| DecoratedModifier
| AbstractModifier
| OverrideModifier
| OptionalModifier
| StaticModifier
'accessor-property':
| PublicOrProtectedOrPrivateModifier
| DecoratedModifier
| AbstractModifier
| OverrideModifier
| StaticModifier
'function-property':
| PublicOrProtectedOrPrivateModifier
| DecoratedModifier
| OverrideModifier
| ReadonlyModifier
| StaticModifier
'set-method':
| PublicOrProtectedOrPrivateModifier
| DecoratedModifier
| AbstractModifier
| OverrideModifier
| StaticModifier
'get-method': AllowedModifiersPerSelector['set-method']
'index-signature': ReadonlyModifier | StaticModifier
constructor: PublicOrProtectedOrPrivateModifier
'static-block': never
}
declare interface BaseSingleCustomGroup<T extends Selector> {
modifiers?: AllowedModifiersPerSelector[T][]
selector?: T
}
declare type ConstructorGroup =
`${PublicOrProtectedOrPrivateModifierPrefix}${ConstructorSelector}`
declare type ConstructorSelector = 'constructor'
declare type CustomGroup = (
| {
order?: SortClassesOptions[0]['order']
type?: SortClassesOptions[0]['type']
}
| {
type?: 'unsorted'
}
) &
(SingleCustomGroup | CustomGroupBlock) & {
groupName: string
declare interface PluginConfig {
rules: {
'sort-variable-declarations': Rule.RuleModule
'sort-intersection-types': Rule.RuleModule
'sort-heritage-clauses': Rule.RuleModule
'sort-array-includes': Rule.RuleModule
'sort-named-imports': Rule.RuleModule
'sort-named-exports': Rule.RuleModule
'sort-object-types': Rule.RuleModule
'sort-union-types': Rule.RuleModule
'sort-switch-case': Rule.RuleModule
'sort-interfaces': Rule.RuleModule
'sort-decorators': Rule.RuleModule
'sort-jsx-props': Rule.RuleModule
'sort-modules': Rule.RuleModule
'sort-classes': Rule.RuleModule
'sort-imports': Rule.RuleModule
'sort-exports': Rule.RuleModule
'sort-objects': Rule.RuleModule
'sort-enums': Rule.RuleModule
'sort-sets': Rule.RuleModule
'sort-maps': Rule.RuleModule
}
declare interface CustomGroupBlock {
anyOf: SingleCustomGroup[]
}
declare type DeclareModifier = 'declare'
declare type DeclareModifierPrefix = WithDashSuffixOrEmpty<DeclareModifier>
declare type DeclarePropertyGroup =
`${DeclareModifierPrefix}${PublicOrProtectedOrPrivateModifierPrefix}${StaticOrAbstractModifierPrefix}${ReadonlyModifierPrefix}${OptionalModifierPrefix}${PropertySelector}`
declare type DecoratedModifier = 'decorated'
declare type DecoratedModifierPrefix = WithDashSuffixOrEmpty<DecoratedModifier>
declare const _default: {
configs: {
'recommended-alphabetical-legacy': ClassicConfig.Config
'recommended-line-length-legacy': ClassicConfig.Config
'recommended-natural-legacy': ClassicConfig.Config
'recommended-alphabetical': FlatConfig.Config
'recommended-line-length': FlatConfig.Config
'recommended-natural': FlatConfig.Config
'recommended-alphabetical-legacy': Linter.LegacyConfig
'recommended-line-length-legacy': Linter.LegacyConfig
'recommended-natural-legacy': Linter.LegacyConfig
'recommended-alphabetical': Linter.Config
'recommended-line-length': Linter.Config
'recommended-natural': Linter.Config
}
rules: {
'sort-variable-declarations': RuleModule<
| 'unexpectedVariableDeclarationsDependencyOrder'
| 'unexpectedVariableDeclarationsOrder',
[
Partial<{
type: 'alphabetical' | 'line-length' | 'natural'
partitionByComment: string[] | boolean | string
specialCharacters: 'remove' | 'trim' | 'keep'
matcher: 'minimatch' | 'regex'
partitionByNewLine: boolean
order: 'desc' | 'asc'
ignoreCase: boolean
}>,
],
unknown,
RuleListener
>
'sort-intersection-types': RuleModule<
| 'unexpectedIntersectionTypesGroupOrder'
| 'unexpectedIntersectionTypesOrder',
[
Partial<{
type: 'alphabetical' | 'line-length' | 'natural'
partitionByComment: string[] | boolean | string
specialCharacters: 'remove' | 'trim' | 'keep'
matcher: 'minimatch' | 'regex'
groups: (
| (
| 'object'
| 'function'
| 'operator'
| 'unknown'
| 'intersection'
| 'conditional'
| 'keyword'
| 'literal'
| 'nullish'
| 'import'
| 'named'
| 'tuple'
| 'union'
)[]
| (
| 'object'
| 'function'
| 'operator'
| 'unknown'
| 'intersection'
| 'conditional'
| 'keyword'
| 'literal'
| 'nullish'
| 'import'
| 'named'
| 'tuple'
| 'union'
)
)[]
partitionByNewLine: boolean
order: 'desc' | 'asc'
ignoreCase: boolean
}>,
],
unknown,
RuleListener
>
'sort-svelte-attributes': RuleModule<
| 'unexpectedSvelteAttributesGroupOrder'
| 'unexpectedSvelteAttributesOrder',
[
Partial<{
customGroups: {
[x: string]: string | string[]
}
type: 'alphabetical' | 'line-length' | 'natural'
specialCharacters: 'remove' | 'trim' | 'keep'
groups: (string | string[])[]
matcher: 'minimatch' | 'regex'
order: 'desc' | 'asc'
ignoreCase: boolean
}>,
],
unknown,
RuleListener
>
'sort-astro-attributes': RuleModule<
'unexpectedAstroAttributesGroupOrder' | 'unexpectedAstroAttributesOrder',
[
Partial<{
customGroups: {
[x: string]: string | string[]
}
type: 'alphabetical' | 'line-length' | 'natural'
specialCharacters: 'remove' | 'trim' | 'keep'
groups: (string | string[])[]
matcher: 'minimatch' | 'regex'
order: 'desc' | 'asc'
ignoreCase: boolean
}>,
],
unknown,
RuleListener
>
'sort-vue-attributes': RuleModule<
'unexpectedVueAttributesGroupOrder' | 'unexpectedVueAttributesOrder',
[
Partial<{
customGroups: {
[x: string]: string | string[]
}
type: 'alphabetical' | 'line-length' | 'natural'
specialCharacters: 'remove' | 'trim' | 'keep'
groups: (string | string[])[]
matcher: 'minimatch' | 'regex'
order: 'desc' | 'asc'
ignoreCase: boolean
}>,
],
unknown,
RuleListener
>
'sort-array-includes': RuleModule<
'unexpectedArrayIncludesOrder',
Options,
unknown,
RuleListener
>
'sort-named-imports': RuleModule<
'unexpectedNamedImportsOrder',
[
Partial<{
groupKind: 'values-first' | 'types-first' | 'mixed'
type: 'alphabetical' | 'line-length' | 'natural'
partitionByComment: string[] | boolean | string
specialCharacters: 'remove' | 'trim' | 'keep'
matcher: 'minimatch' | 'regex'
partitionByNewLine: boolean
order: 'desc' | 'asc'
ignoreAlias: boolean
ignoreCase: boolean
}>,
],
unknown,
RuleListener
>
'sort-named-exports': RuleModule<
'unexpectedNamedExportsOrder',
[
Partial<{
groupKind: 'values-first' | 'types-first' | 'mixed'
type: 'alphabetical' | 'line-length' | 'natural'
partitionByComment: string[] | boolean | string
specialCharacters: 'remove' | 'trim' | 'keep'
matcher: 'minimatch' | 'regex'
partitionByNewLine: boolean
order: 'desc' | 'asc'
ignoreCase: boolean
}>,
],
unknown,
RuleListener
>
'sort-object-types': RuleModule<
'unexpectedObjectTypesGroupOrder' | 'unexpectedObjectTypesOrder',
[
Partial<{
groupKind: 'required-first' | 'optional-first' | 'mixed'
customGroups: {
[x: string]: string | string[]
}
type: 'alphabetical' | 'line-length' | 'natural'
partitionByComment: string[] | boolean | string
specialCharacters: 'remove' | 'trim' | 'keep'
groups: (string | string[])[]
matcher: 'minimatch' | 'regex'
partitionByNewLine: boolean
order: 'desc' | 'asc'
ignoreCase: boolean
}>,
],
unknown,
RuleListener
>
'sort-union-types': RuleModule<
'unexpectedUnionTypesGroupOrder' | 'unexpectedUnionTypesOrder',
[
Partial<{
type: 'alphabetical' | 'line-length' | 'natural'
partitionByComment: string[] | boolean | string
specialCharacters: 'remove' | 'trim' | 'keep'
matcher: 'minimatch' | 'regex'
groups: (
| (
| 'object'
| 'function'
| 'operator'
| 'unknown'
| 'intersection'
| 'conditional'
| 'keyword'
| 'literal'
| 'nullish'
| 'import'
| 'named'
| 'tuple'
| 'union'
)[]
| (
| 'object'
| 'function'
| 'operator'
| 'unknown'
| 'intersection'
| 'conditional'
| 'keyword'
| 'literal'
| 'nullish'
| 'import'
| 'named'
| 'tuple'
| 'union'
)
)[]
partitionByNewLine: boolean
order: 'desc' | 'asc'
ignoreCase: boolean
}>,
],
unknown,
RuleListener
>
'sort-switch-case': RuleModule<
'unexpectedSwitchCaseOrder',
[
Partial<{
type: 'alphabetical' | 'line-length' | 'natural'
specialCharacters: 'remove' | 'trim' | 'keep'
order: 'desc' | 'asc'
ignoreCase: boolean
}>,
],
unknown,
RuleListener
>
'sort-interfaces': RuleModule<
| 'unexpectedInterfacePropertiesGroupOrder'
| 'unexpectedInterfacePropertiesOrder',
[
Partial<{
groupKind: 'optional-first' | 'required-first' | 'mixed'
customGroups: {
[key: string]: string[] | string
}
type: 'alphabetical' | 'line-length' | 'natural'
partitionByComment: string[] | boolean | string
specialCharacters: 'remove' | 'trim' | 'keep'
groups: (string | string[])[]
matcher: 'minimatch' | 'regex'
partitionByNewLine: boolean
ignorePattern: string[]
order: 'desc' | 'asc'
ignoreCase: boolean
}>,
],
unknown,
RuleListener
>
'sort-jsx-props': RuleModule<
'unexpectedJSXPropsGroupOrder' | 'unexpectedJSXPropsOrder',
[
Partial<{
customGroups: {
[x: string]: string | string[]
}
type: 'alphabetical' | 'line-length' | 'natural'
specialCharacters: 'remove' | 'trim' | 'keep'
groups: (string | string[])[]
matcher: 'minimatch' | 'regex'
ignorePattern: string[]
order: 'desc' | 'asc'
ignoreCase: boolean
}>,
],
unknown,
RuleListener
>
'sort-classes': RuleModule<
| 'unexpectedClassesDependencyOrder'
| 'unexpectedClassesGroupOrder'
| 'unexpectedClassesOrder',
SortClassesOptions,
unknown,
RuleListener
>
'sort-imports': RuleModule<
MESSAGE_ID,
Options_2<string[]>,
unknown,
RuleListener
>
'sort-exports': RuleModule<
'unexpectedExportsOrder',
[
Partial<{
groupKind: 'values-first' | 'types-first' | 'mixed'
type: 'alphabetical' | 'line-length' | 'natural'
partitionByComment: string[] | boolean | string
specialCharacters: 'remove' | 'trim' | 'keep'
matcher: 'minimatch' | 'regex'
partitionByNewLine: boolean
order: 'desc' | 'asc'
ignoreCase: boolean
}>,
],
unknown,
RuleListener
>
'sort-objects': RuleModule<
| 'unexpectedObjectsDependencyOrder'
| 'unexpectedObjectsGroupOrder'
| 'unexpectedObjectsOrder',
[
Partial<{
customGroups: {
[key: string]: string[] | string
}
type: 'alphabetical' | 'line-length' | 'natural'
partitionByComment: string[] | boolean | string
specialCharacters: 'remove' | 'trim' | 'keep'
matcher: 'minimatch' | 'regex'
groups: (string[] | string)[]
partitionByNewLine: boolean
styledComponents: boolean
destructureOnly: boolean
ignorePattern: string[]
order: 'desc' | 'asc'
ignoreCase: boolean
}>,
],
unknown,
RuleListener
>
'sort-enums': RuleModule<
'unexpectedEnumsDependencyOrder' | 'unexpectedEnumsOrder',
Options_3,
unknown,
RuleListener
>
'sort-sets': RuleModule<
'unexpectedSetsOrder',
Options,
unknown,
RuleListener
>
'sort-maps': RuleModule<
'unexpectedMapElementsOrder',
[
Partial<{
type: 'alphabetical' | 'line-length' | 'natural'
partitionByComment: string[] | boolean | string
specialCharacters: 'remove' | 'trim' | 'keep'
matcher: 'minimatch' | 'regex'
partitionByNewLine: boolean
order: 'desc' | 'asc'
ignoreCase: boolean
}>,
],
unknown,
RuleListener
>
}
name: string
}
declare type FunctionPropertyGroup =
`${PublicOrProtectedOrPrivateModifierPrefix}${StaticModifierPrefix}${OverrideModifierPrefix}${ReadonlyModifierPrefix}${DecoratedModifierPrefix}${FunctionPropertySelector}`
declare type FunctionPropertySelector = 'function-property'
declare type GetMethodOrSetMethodGroup =
`${PublicOrProtectedOrPrivateModifierPrefix}${StaticOrAbstractModifierPrefix}${OverrideModifierPrefix}${DecoratedModifierPrefix}${GetMethodOrSetMethodSelector}`
declare type GetMethodOrSetMethodSelector =
| GetMethodSelector
| SetMethodSelector
declare type GetMethodSelector = 'get-method'
/**
* Some invalid combinations are still handled by this type, such as
* - private abstract X
* - abstract decorated X
* Only used in code, so I don't know if it's worth maintaining this.
*/
declare type Group =
| GetMethodOrSetMethodGroup
| NonDeclarePropertyGroup
| AccessorPropertyGroup
| FunctionPropertyGroup
| DeclarePropertyGroup
| IndexSignatureGroup
| ConstructorGroup
| StaticBlockGroup
| MethodGroup
| 'unknown'
| string
declare type Group_2<T extends string[]> =
| 'side-effect-style'
| 'external-type'
| 'internal-type'
| 'builtin-type'
| 'sibling-type'
| 'parent-type'
| 'side-effect'
| 'index-type'
| 'internal'
| 'external'
| T[number]
| 'sibling'
| 'unknown'
| 'builtin'
| 'parent'
| 'object'
| 'index'
| 'style'
| 'type'
declare type IndexSignatureGroup =
`${StaticModifierPrefix}${ReadonlyModifierPrefix}${IndexSignatureSelector}`
declare type IndexSignatureSelector = 'index-signature'
declare type MESSAGE_ID =
| 'missedSpacingBetweenImports'
| 'unexpectedImportsGroupOrder'
| 'extraSpacingBetweenImports'
| 'unexpectedImportsOrder'
declare type MethodGroup =
`${PublicOrProtectedOrPrivateModifierPrefix}${StaticOrAbstractModifierPrefix}${OverrideModifierPrefix}${DecoratedModifierPrefix}${OptionalModifierPrefix}${MethodSelector}`
declare type MethodSelector = 'method'
declare type NonDeclarePropertyGroup =
`${PublicOrProtectedOrPrivateModifierPrefix}${StaticOrAbstractModifierPrefix}${OverrideModifierPrefix}${ReadonlyModifierPrefix}${DecoratedModifierPrefix}${OptionalModifierPrefix}${PropertySelector}`
declare type OptionalModifier = 'optional'
declare type OptionalModifierPrefix = WithDashSuffixOrEmpty<OptionalModifier>
declare type Options = [
Partial<{
groupKind: 'literals-first' | 'spreads-first' | 'mixed'
type: 'alphabetical' | 'line-length' | 'natural'
partitionByComment: string[] | boolean | string
specialCharacters: 'remove' | 'trim' | 'keep'
matcher: 'minimatch' | 'regex'
partitionByNewLine: boolean
order: 'desc' | 'asc'
ignoreCase: boolean
}>,
]
declare type Options_2<T extends string[]> = [
Partial<{
customGroups: {
value?: {
[key in T[number]]: string[] | string
}
type?: {
[key in T[number]]: string[] | string
}
}
type: 'alphabetical' | 'line-length' | 'natural'
newlinesBetween: 'ignore' | 'always' | 'never'
specialCharacters: 'remove' | 'trim' | 'keep'
groups: (Group_2<T>[] | Group_2<T>)[]
matcher: 'minimatch' | 'regex'
environment: 'node' | 'bun'
internalPattern: string[]
sortSideEffects: boolean
maxLineLength?: number
order: 'desc' | 'asc'
ignoreCase: boolean
}>,
]
declare type Options_3 = [
Partial<{
type: 'alphabetical' | 'line-length' | 'natural'
partitionByComment: string[] | boolean | string
specialCharacters: 'remove' | 'trim' | 'keep'
matcher: 'minimatch' | 'regex'
partitionByNewLine: boolean
forceNumericSort: boolean
order: 'desc' | 'asc'
sortByValue: boolean
ignoreCase: boolean
}>,
]
declare type OverrideModifier = 'override'
declare type OverrideModifierPrefix = WithDashSuffixOrEmpty<OverrideModifier>
declare type PrivateModifier = 'private'
declare type PropertySelector = 'property'
declare type ProtectedModifier = 'protected'
declare type PublicModifier = 'public'
declare type PublicOrProtectedOrPrivateModifier =
| ProtectedModifier
| PrivateModifier
| PublicModifier
declare type PublicOrProtectedOrPrivateModifierPrefix = WithDashSuffixOrEmpty<
ProtectedModifier | PrivateModifier | PublicModifier
>
declare type ReadonlyModifier = 'readonly'
declare type ReadonlyModifierPrefix = WithDashSuffixOrEmpty<ReadonlyModifier>
declare type Selector =
| AccessorPropertySelector
| FunctionPropertySelector
| IndexSignatureSelector
| ConstructorSelector
| StaticBlockSelector
| GetMethodSelector
| SetMethodSelector
| PropertySelector
| MethodSelector
declare type SetMethodSelector = 'set-method'
declare type SingleCustomGroup =
| AdvancedSingleCustomGroup<FunctionPropertySelector>
| AdvancedSingleCustomGroup<AccessorPropertySelector>
| BaseSingleCustomGroup<IndexSignatureSelector>
| AdvancedSingleCustomGroup<GetMethodSelector>
| AdvancedSingleCustomGroup<SetMethodSelector>
| AdvancedSingleCustomGroup<PropertySelector>
| BaseSingleCustomGroup<StaticBlockSelector>
| BaseSingleCustomGroup<ConstructorSelector>
| AdvancedSingleCustomGroup<MethodSelector>
declare type SortClassesOptions = [
Partial<{
customGroups:
| {
[key: string]: string[] | string
}
| CustomGroup[]
type: 'alphabetical' | 'line-length' | 'natural'
partitionByComment: string[] | boolean | string
specialCharacters: 'remove' | 'trim' | 'keep'
matcher: 'minimatch' | 'regex'
groups: (Group[] | Group)[]
order: 'desc' | 'asc'
ignoreCase: boolean
}>,
]
declare type StaticBlockGroup = `${StaticBlockSelector}`
declare type StaticBlockSelector = 'static-block'
declare type StaticModifier = 'static'
declare type StaticModifierPrefix = WithDashSuffixOrEmpty<StaticModifier>
declare type StaticOrAbstractModifierPrefix = WithDashSuffixOrEmpty<
AbstractModifier | StaticModifier
>
declare type WithDashSuffixOrEmpty<T extends string> = `${T}-` | ''
export {}
export = _default
'use strict'
const sortVariableDeclarations = require('./rules/sort-variable-declarations.js')
const sortIntersectionTypes = require('./rules/sort-intersection-types.js')
const sortSvelteAttributes = require('./rules/sort-svelte-attributes.js')
const sortAstroAttributes = require('./rules/sort-astro-attributes.js')
const sortHeritageClauses = require('./rules/sort-heritage-clauses.js')
const sortArrayIncludes = require('./rules/sort-array-includes.js')
const sortVueAttributes = require('./rules/sort-vue-attributes.js')
const sortNamedImports = require('./rules/sort-named-imports.js')

@@ -14,2 +12,3 @@ const sortNamedExports = require('./rules/sort-named-exports.js')

const sortInterfaces = require('./rules/sort-interfaces.js')
const sortDecorators = require('./rules/sort-decorators.js')
const sortJsxProps = require('./rules/sort-jsx-props.js')

@@ -20,2 +19,3 @@ const sortClasses = require('./rules/sort-classes.js')

const sortObjects = require('./rules/sort-objects.js')
const sortModules = require('./rules/sort-modules.js')
const sortEnums = require('./rules/sort-enums.js')

@@ -29,5 +29,3 @@ const sortMaps = require('./rules/sort-maps.js')

'sort-intersection-types': sortIntersectionTypes,
'sort-svelte-attributes': sortSvelteAttributes,
'sort-astro-attributes': sortAstroAttributes,
'sort-vue-attributes': sortVueAttributes,
'sort-heritage-clauses': sortHeritageClauses,
'sort-array-includes': sortArrayIncludes.default,

@@ -39,4 +37,6 @@ 'sort-named-imports': sortNamedImports,

'sort-switch-case': sortSwitchCase,
'sort-decorators': sortDecorators,
'sort-interfaces': sortInterfaces,
'sort-jsx-props': sortJsxProps,
'sort-modules': sortModules,
'sort-classes': sortClasses,

@@ -54,9 +54,6 @@ 'sort-imports': sortImports,

Object.fromEntries(
Object.entries(plugin.rules).reduce(
(accumulator, [ruleName, ruleValue]) =>
ruleValue.meta.deprecated
? accumulator
: [...accumulator, [`${name}/${ruleName}`, ['error', options]]],
[],
),
Object.keys(plugin.rules).map(ruleName => [
`${name}/${ruleName}`,
['error', options],
]),
)

@@ -63,0 +60,0 @@ let createConfig = options => ({

@@ -6,2 +6,5 @@ 'use strict'

})
const commonJsonSchemas = require('../utils/common-json-schemas.js')
const getEslintDisabledLines = require('../utils/get-eslint-disabled-lines.js')
const isNodeEslintDisabled = require('../utils/is-node-eslint-disabled.js')
const isPartitionComment = require('../utils/is-partition-comment.js')

@@ -11,3 +14,2 @@ const getCommentsBefore = require('../utils/get-comments-before.js')

const getLinesBetween = require('../utils/get-lines-between.js')
const getGroupNumber = require('../utils/get-group-number.js')
const getSourceCode = require('../utils/get-source-code.js')

@@ -17,3 +19,3 @@ const toSingleLine = require('../utils/to-single-line.js')

const getSettings = require('../utils/get-settings.js')
const isPositive = require('../utils/is-positive.js')
const isSortable = require('../utils/is-sortable.js')
const sortNodes = require('../utils/sort-nodes.js')

@@ -23,55 +25,24 @@ const makeFixes = require('../utils/make-fixes.js')

const pairwise = require('../utils/pairwise.js')
const compare = require('../utils/compare.js')
let defaultOptions = {
groupKind: 'literals-first',
specialCharacters: 'keep',
partitionByComment: false,
partitionByNewLine: false,
type: 'alphabetical',
ignoreCase: true,
locales: 'en-US',
order: 'asc',
}
let jsonSchema = {
type: 'object',
properties: {
type: {
description: 'Specifies the sorting method.',
type: 'string',
enum: ['alphabetical', 'natural', 'line-length'],
},
order: {
partitionByComment: {
...commonJsonSchemas.partitionByCommentJsonSchema,
description:
'Determines whether the sorted items should be in ascending or descending order.',
type: 'string',
enum: ['asc', 'desc'],
'Allows you to use comments to separate the array members into logical groups.',
},
matcher: {
description: 'Specifies the string matcher.',
type: 'string',
enum: ['minimatch', 'regex'],
},
ignoreCase: {
description: 'Controls whether sorting should be case-sensitive or not.',
type: 'boolean',
},
specialCharacters: {
description:
'Controls how special characters should be handled before sorting.',
type: 'string',
enum: ['remove', 'trim', 'keep'],
},
groupKind: {
enum: ['mixed', 'literals-first', 'spreads-first'],
description: 'Specifies top-level groups.',
enum: ['mixed', 'literals-first', 'spreads-first'],
type: 'string',
},
partitionByComment: {
description:
'Allows you to use comments to separate the array members into logical groups.',
anyOf: [
{
type: 'array',
items: {
type: 'string',
},
},
{
type: 'boolean',
},
{
type: 'string',
},
],
},
partitionByNewLine: {

@@ -82,31 +53,12 @@ description:

},
specialCharacters: commonJsonSchemas.specialCharactersJsonSchema,
ignoreCase: commonJsonSchemas.ignoreCaseJsonSchema,
locales: commonJsonSchemas.localesJsonSchema,
order: commonJsonSchemas.orderJsonSchema,
type: commonJsonSchemas.typeJsonSchema,
},
additionalProperties: false,
type: 'object',
}
const sortArrayIncludes = createEslintRule.createEslintRule({
name: 'sort-array-includes',
meta: {
type: 'suggestion',
docs: {
description: 'Enforce sorted arrays before include method.',
},
fixable: 'code',
schema: [jsonSchema],
messages: {
unexpectedArrayIncludesOrder:
'Expected "{{right}}" to come before "{{left}}".',
},
},
defaultOptions: [
{
type: 'alphabetical',
order: 'asc',
ignoreCase: true,
specialCharacters: 'keep',
matcher: 'minimatch',
groupKind: 'literals-first',
partitionByComment: false,
partitionByNewLine: false,
},
],
create: context => ({

@@ -128,109 +80,133 @@ MemberExpression: node => {

}),
meta: {
docs: {
description: 'Enforce sorted arrays before include method.',
url: 'https://perfectionist.dev/rules/sort-array-includes',
recommended: true,
},
messages: {
unexpectedArrayIncludesOrder:
'Expected "{{right}}" to come before "{{left}}".',
},
schema: [jsonSchema],
type: 'suggestion',
fixable: 'code',
},
defaultOptions: [defaultOptions],
name: 'sort-array-includes',
})
let sortArray = (context, messageId, elements) => {
if (!isSortable.isSortable(elements)) {
return
}
let settings = getSettings.getSettings(context.settings)
if (elements.length > 1) {
let options = complete.complete(context.options.at(0), settings, {
groupKind: 'literals-first',
type: 'alphabetical',
ignoreCase: true,
specialCharacters: 'keep',
matcher: 'minimatch',
order: 'asc',
partitionByComment: false,
partitionByNewLine: false,
})
let sourceCode = getSourceCode.getSourceCode(context)
let partitionComment = options.partitionByComment
let formattedMembers = elements.reduce(
(accumulator, element) => {
var _a
if (element !== null) {
let group = 'unknown'
if (typeof options.groupKind === 'string') {
group = element.type === 'SpreadElement' ? 'spread' : 'literal'
}
let lastSortingNode =
(_a = accumulator.at(-1)) == null ? void 0 : _a.at(-1)
let sortingNode = {
name:
element.type === 'Literal'
? `${element.value}`
: sourceCode.text.slice(...element.range),
size: rangeToDiff.rangeToDiff(element.range),
node: element,
group,
}
if (
(partitionComment &&
isPartitionComment.hasPartitionComment(
partitionComment,
getCommentsBefore.getCommentsBefore(element, sourceCode),
options.matcher,
)) ||
(options.partitionByNewLine &&
lastSortingNode &&
getLinesBetween.getLinesBetween(
sourceCode,
lastSortingNode,
sortingNode,
))
) {
accumulator.push([])
}
accumulator.at(-1).push(sortingNode)
}
let options = complete.complete(
context.options.at(0),
settings,
defaultOptions,
)
let sourceCode = getSourceCode.getSourceCode(context)
let eslintDisabledLines = getEslintDisabledLines.getEslintDisabledLines({
ruleName: context.id,
sourceCode,
})
let formattedMembers = elements.reduce(
(accumulator, element) => {
var _a
if (element === null) {
return accumulator
},
[[]],
}
let lastSortingNode =
(_a = accumulator.at(-1)) == null ? void 0 : _a.at(-1)
let sortingNode = {
name:
element.type === 'Literal'
? `${element.value}`
: sourceCode.getText(element),
isEslintDisabled: isNodeEslintDisabled.isNodeEslintDisabled(
element,
eslintDisabledLines,
),
groupKind: element.type === 'SpreadElement' ? 'spread' : 'literal',
size: rangeToDiff.rangeToDiff(element, sourceCode),
node: element,
}
if (
(options.partitionByComment &&
isPartitionComment.hasPartitionComment(
options.partitionByComment,
getCommentsBefore.getCommentsBefore(element, sourceCode),
)) ||
(options.partitionByNewLine &&
lastSortingNode &&
getLinesBetween.getLinesBetween(
sourceCode,
lastSortingNode,
sortingNode,
))
) {
accumulator.push([])
}
accumulator.at(-1).push(sortingNode)
return accumulator
},
[[]],
)
let groupKindOrder
if (options.groupKind === 'literals-first') {
groupKindOrder = ['literal', 'spread']
} else if (options.groupKind === 'spreads-first') {
groupKindOrder = ['spread', 'literal']
} else {
groupKindOrder = ['any']
}
for (let nodes of formattedMembers) {
let filteredGroupKindNodes = groupKindOrder.map(groupKind =>
nodes.filter(
currentNode =>
groupKind === 'any' || currentNode.groupKind === groupKind,
),
)
for (let nodes of formattedMembers) {
pairwise.pairwise(nodes, (left, right) => {
let groupKindOrder = ['unknown']
if (typeof options.groupKind === 'string') {
groupKindOrder =
options.groupKind === 'literals-first'
? ['literal', 'spread']
: ['spread', 'literal']
}
let leftNum = getGroupNumber.getGroupNumber(groupKindOrder, left)
let rightNum = getGroupNumber.getGroupNumber(groupKindOrder, right)
if (
(options.groupKind !== 'mixed' && leftNum > rightNum) ||
((options.groupKind === 'mixed' || leftNum === rightNum) &&
isPositive.isPositive(compare.compare(left, right, options)))
) {
context.report({
messageId,
data: {
left: toSingleLine.toSingleLine(left.name),
right: toSingleLine.toSingleLine(right.name),
},
node: right.node,
fix: fixer => {
let sortedNodes =
options.groupKind !== 'mixed'
? groupKindOrder
.map(group => nodes.filter(n => n.group === group))
.map(groupedNodes =>
sortNodes.sortNodes(groupedNodes, options),
)
.flat()
: sortNodes.sortNodes(nodes, options)
return makeFixes.makeFixes(
fixer,
nodes,
sortedNodes,
sourceCode,
options,
)
},
})
}
let sortNodesIgnoringEslintDisabledNodes = ignoreEslintDisabledNodes =>
filteredGroupKindNodes.flatMap(groupedNodes =>
sortNodes.sortNodes(groupedNodes, options, {
ignoreEslintDisabledNodes,
}),
)
let sortedNodes = sortNodesIgnoringEslintDisabledNodes(false)
let sortedNodesExcludingEslintDisabled =
sortNodesIgnoringEslintDisabledNodes(true)
pairwise.pairwise(nodes, (left, right) => {
let indexOfLeft = sortedNodes.indexOf(left)
let indexOfRight = sortedNodes.indexOf(right)
let indexOfRightExcludingEslintDisabled =
sortedNodesExcludingEslintDisabled.indexOf(right)
if (
indexOfLeft < indexOfRight &&
indexOfLeft < indexOfRightExcludingEslintDisabled
) {
return
}
context.report({
fix: fixer =>
makeFixes.makeFixes({
sortedNodes: sortedNodesExcludingEslintDisabled,
sourceCode,
options,
fixer,
nodes,
}),
data: {
right: toSingleLine.toSingleLine(right.name),
left: toSingleLine.toSingleLine(left.name),
},
node: right.node,
messageId,
})
}
})
}
}
exports.default = sortArrayIncludes
exports.defaultOptions = defaultOptions
exports.jsonSchema = jsonSchema
exports.sortArray = sortArray
'use strict'
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
const validateGroupsConfiguration$1 = require('../utils/validate-groups-configuration.js')
const sortClasses_types = require('./sort-classes.types.js')
const isSortable = require('../utils/is-sortable.js')
const matches = require('../utils/matches.js')
const cachedGroupsByModifiersAndSelectors = /* @__PURE__ */ new Map()
const generateOfficialGroups = (modifiers, selectors) => {
let modifiersAndSelectorsKey = modifiers.join('&') + '/' + selectors.join('&')
let cachedValue = cachedGroupsByModifiersAndSelectors.get(
modifiersAndSelectorsKey,
)
if (cachedValue) {
return cachedValue
}
let allModifiersCombinations = []
for (let i = modifiers.length; i > 0; i--) {
allModifiersCombinations = [
...allModifiersCombinations,
...getCombinations(modifiers, i),
]
}
let allModifiersCombinationPermutations = allModifiersCombinations.flatMap(
result => getPermutations(result),
)
let returnValue = []
for (let selector of selectors) {
returnValue = [
...returnValue,
...allModifiersCombinationPermutations.map(
modifiersCombinationPermutation =>
[...modifiersCombinationPermutation, selector].join('-'),
),
selector,
]
}
cachedGroupsByModifiersAndSelectors.set(modifiersAndSelectorsKey, returnValue)
return returnValue
}
const getCombinations = (array, n) => {
let result = []
let backtrack = (start, comb) => {
if (comb.length === n) {
result.push([...comb])
return
}
for (let i = start; i < array.length; i++) {
comb.push(array[i])
backtrack(i + 1, comb)
comb.pop()
}
}
backtrack(0, [])
return result
}
const getPermutations = elements => {
let result = []
let backtrack = first => {
if (first === elements.length) {
result.push([...elements])
return
}
for (let i = first; i < elements.length; i++) {
;[elements[first], elements[i]] = [elements[i], elements[first]]
backtrack(first + 1)
;[elements[first], elements[i]] = [elements[i], elements[first]]
}
}
backtrack(0)
return result
}
const getOverloadSignatureGroups = members => {
let getOverloadSignatureGroups = members => {
let methods = members

@@ -99,5 +33,5 @@ .filter(

...staticOverloadSignaturesByName.values(),
].filter(group => group.length > 1)
].filter(isSortable.isSortable)
}
const customGroupMatches = props => {
let customGroupMatches = props => {
if ('anyOf' in props.customGroup) {

@@ -128,3 +62,2 @@ return props.customGroup.anyOf.some(subgroup =>

props.customGroup.elementNamePattern,
props.matcher,
)

@@ -142,3 +75,2 @@ if (!matchesElementNamePattern) {

props.customGroup.elementValuePattern,
props.matcher,
)

@@ -155,3 +87,3 @@ if (!matchesElementValuePattern) {

let matchesDecoratorNamePattern = props.decorators.some(decorator =>
matches.matches(decorator, decoratorPattern, props.matcher),
matches.matches(decorator, decoratorPattern),
)

@@ -164,7 +96,9 @@ if (!matchesDecoratorNamePattern) {

}
const getCompareOptions = (options, groupNumber) => {
let getCompareOptions = (options, groupNumber) => {
let group = options.groups[groupNumber]
let customGroup =
typeof group === 'string' && Array.isArray(options.customGroups)
? options.customGroups.find(g => group === g.groupName)
typeof group === 'string'
? options.customGroups.find(
currentGroup => group === currentGroup.groupName,
)
: null

@@ -175,3 +109,2 @@ if ((customGroup == null ? void 0 : customGroup.type) === 'unsorted') {

return {
type: (customGroup == null ? void 0 : customGroup.type) ?? options.type,
order:

@@ -182,50 +115,9 @@ customGroup && 'order' in customGroup && customGroup.order

specialCharacters: options.specialCharacters,
type: (customGroup == null ? void 0 : customGroup.type) ?? options.type,
ignoreCase: options.ignoreCase,
locales: options.locales,
}
}
let validateGroupsConfiguration = (groups, customGroups) => {
let availableCustomGroupNames = Array.isArray(customGroups)
? customGroups.map(customGroup => customGroup.groupName)
: Object.keys(customGroups)
let invalidGroups = groups
.flat()
.filter(
group =>
!isPredefinedGroup(group) && !availableCustomGroupNames.includes(group),
)
if (invalidGroups.length) {
throw new Error('Invalid group(s): ' + invalidGroups.join(', '))
}
validateGroupsConfiguration$1.validateNoDuplicatedGroups(groups)
}
const isPredefinedGroup = input => {
if (input === 'unknown') {
return true
}
let singleWordSelector = input.split('-').at(-1)
if (!singleWordSelector) {
return false
}
let twoWordsSelector = input.split('-').slice(-2).join('-')
let isTwoWordSelectorValid =
sortClasses_types.allSelectors.includes(twoWordsSelector)
if (
!sortClasses_types.allSelectors.includes(singleWordSelector) &&
!isTwoWordSelectorValid
) {
return false
}
let modifiers = input.split('-').slice(0, isTwoWordSelectorValid ? -2 : -1)
return (
new Set(modifiers).size === modifiers.length &&
modifiers.every(modifier =>
sortClasses_types.allModifiers.includes(modifier),
)
)
}
exports.customGroupMatches = customGroupMatches
exports.generateOfficialGroups = generateOfficialGroups
exports.getCombinations = getCombinations
exports.getCompareOptions = getCompareOptions
exports.getOverloadSignatureGroups = getOverloadSignatureGroups
exports.validateGroupsConfiguration = validateGroupsConfiguration
'use strict'
const sortClassesUtils = require('./sort-classes-utils.js')
const commonJsonSchemas = require('../utils/common-json-schemas.js')
const sortClasses_types = require('./sort-classes.types.js')
const sortNodesByDependencies = require('../utils/sort-nodes-by-dependencies.js')
const validateNewlinesAndPartitionConfiguration = require('../utils/validate-newlines-and-partition-configuration.js')
const sortClassesUtils = require('./sort-classes-utils.js')
const validateGeneratedGroupsConfiguration = require('./validate-generated-groups-configuration.js')
const generatePredefinedGroups = require('../utils/generate-predefined-groups.js')
const getEslintDisabledLines = require('../utils/get-eslint-disabled-lines.js')
const isNodeEslintDisabled = require('../utils/is-node-eslint-disabled.js')
const isPartitionComment = require('../utils/is-partition-comment.js')
const sortNodesByGroups = require('../utils/sort-nodes-by-groups.js')
const getCommentsBefore = require('../utils/get-comments-before.js')
const makeNewlinesFixes = require('../utils/make-newlines-fixes.js')
const getNewlinesErrors = require('../utils/get-newlines-errors.js')
const createEslintRule = require('../utils/create-eslint-rule.js')
const getLinesBetween = require('../utils/get-lines-between.js')
const getGroupNumber = require('../utils/get-group-number.js')

@@ -14,2 +23,3 @@ const getSourceCode = require('../utils/get-source-code.js')

const getSettings = require('../utils/get-settings.js')
const isSortable = require('../utils/is-sortable.js')
const useGroups = require('../utils/use-groups.js')

@@ -19,604 +29,630 @@ const makeFixes = require('../utils/make-fixes.js')

const pairwise = require('../utils/pairwise.js')
const matches = require('../utils/matches.js')
let cachedGroupsByModifiersAndSelectors = /* @__PURE__ */ new Map()
let 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',
],
ignoreCallbackDependenciesPatterns: [],
partitionByComment: false,
partitionByNewLine: false,
newlinesBetween: 'ignore',
specialCharacters: 'keep',
type: 'alphabetical',
ignoreCase: true,
customGroups: [],
locales: 'en-US',
order: 'asc',
}
const sortClasses = createEslintRule.createEslintRule({
name: 'sort-classes',
meta: {
type: 'suggestion',
docs: {
description: 'Enforce sorted classes.',
},
fixable: 'code',
schema: [
{
type: 'object',
properties: {
type: {
description: 'Specifies the sorting method.',
type: 'string',
enum: ['alphabetical', 'natural', 'line-length'],
},
order: {
description:
'Determines whether the sorted items should be in ascending or descending order.',
type: 'string',
enum: ['asc', 'desc'],
},
matcher: {
description: 'Specifies the string matcher.',
type: 'string',
enum: ['minimatch', 'regex'],
},
ignoreCase: {
description:
'Controls whether sorting should be case-sensitive or not.',
type: 'boolean',
},
specialCharacters: {
description:
'Controls how special characters should be handled before sorting.',
type: 'string',
enum: ['remove', 'trim', 'keep'],
},
partitionByComment: {
description:
'Allows to use comments to separate the class members into logical groups.',
anyOf: [
{
type: 'array',
items: {
type: 'string',
},
},
{
type: 'boolean',
},
{
type: 'string',
},
],
},
groups: {
description: 'Specifies the order of the groups.',
type: 'array',
items: {
oneOf: [
{
type: 'string',
},
{
type: 'array',
items: {
type: 'string',
},
},
],
},
},
customGroups: {
description: 'Specifies custom groups.',
oneOf: [
{
type: 'object',
additionalProperties: {
oneOf: [
{
type: 'string',
},
{
type: 'array',
items: {
type: 'string',
},
},
],
},
},
{
type: 'array',
items: {
description: 'Advanced custom groups.',
oneOf: [
{
description: 'Custom group block.',
type: 'object',
additionalProperties: false,
properties: {
...sortClasses_types.customGroupNameJsonSchema,
...sortClasses_types.customGroupSortJsonSchema,
anyOf: {
type: 'array',
items: {
description: 'Custom group.',
type: 'object',
additionalProperties: false,
properties: {
...sortClasses_types.singleCustomGroupJsonSchema,
},
},
},
},
},
{
description: 'Custom group.',
type: 'object',
additionalProperties: false,
properties: {
...sortClasses_types.customGroupNameJsonSchema,
...sortClasses_types.customGroupSortJsonSchema,
...sortClasses_types.singleCustomGroupJsonSchema,
},
},
],
},
},
],
},
},
additionalProperties: false,
},
],
messages: {
unexpectedClassesGroupOrder:
'Expected "{{right}}" ({{rightGroup}}) to come before "{{left}}" ({{leftGroup}}).',
unexpectedClassesOrder: 'Expected "{{right}}" to come before "{{left}}".',
unexpectedClassesDependencyOrder:
'Expected dependency "{{right}}" to come before "{{nodeDependentOnRight}}".',
},
},
defaultOptions: [
{
type: 'alphabetical',
order: 'asc',
ignoreCase: true,
specialCharacters: 'keep',
matcher: 'minimatch',
partitionByComment: false,
groups: [
'index-signature',
'static-property',
'static-block',
['protected-property', 'protected-accessor-property'],
['private-property', 'private-accessor-property'],
['property', 'accessor-property'],
'constructor',
'static-method',
'protected-method',
'private-method',
'method',
['get-method', 'set-method'],
'unknown',
],
customGroups: [],
},
],
create: context => ({
ClassBody: node => {
var _a
if (node.body.length > 1) {
let settings = getSettings.getSettings(context.settings)
let options = complete.complete(context.options.at(0), settings, {
groups: [
'index-signature',
'static-property',
'static-block',
['protected-property', 'protected-accessor-property'],
['private-property', 'private-accessor-property'],
['property', 'accessor-property'],
'constructor',
'static-method',
'protected-method',
'private-method',
'method',
['get-method', 'set-method'],
'unknown',
],
matcher: 'minimatch',
partitionByComment: false,
type: 'alphabetical',
ignoreCase: true,
specialCharacters: 'keep',
customGroups: [],
order: 'asc',
})
sortClassesUtils.validateGroupsConfiguration(
options.groups,
options.customGroups,
)
let sourceCode = getSourceCode.getSourceCode(context)
let className = (_a = node.parent.id) == null ? void 0 : _a.name
let getDependencyName = (nodeName, isStatic) =>
`${isStatic ? 'static ' : ''}${nodeName}`
let classMethodsDependencyNames = new Set(
node.body
.map(member => {
if (
(member.type === 'MethodDefinition' ||
member.type === 'TSAbstractMethodDefinition') &&
'name' in member.key
) {
return getDependencyName(member.key.name, member.static)
}
return null
})
.filter(m => m !== null),
)
let extractDependencies = (expression, isMemberStatic) => {
let dependencies = []
let checkNode = nodeValue => {
if (!isSortable.isSortable(node.body)) {
return
}
let settings = getSettings.getSettings(context.settings)
let options = complete.complete(
context.options.at(0),
settings,
defaultOptions,
)
validateGeneratedGroupsConfiguration.validateGeneratedGroupsConfiguration(
{
customGroups: options.customGroups,
modifiers: sortClasses_types.allModifiers,
selectors: sortClasses_types.allSelectors,
groups: options.groups,
},
)
validateNewlinesAndPartitionConfiguration.validateNewlinesAndPartitionConfiguration(
options,
)
let sourceCode = getSourceCode.getSourceCode(context)
let eslintDisabledLines = getEslintDisabledLines.getEslintDisabledLines({
ruleName: context.id,
sourceCode,
})
let className = (_a = node.parent.id) == null ? void 0 : _a.name
let getDependencyName = props =>
`${props.isStatic ? 'static ' : ''}${props.isPrivateHash ? '#' : ''}${props.nodeNameWithoutStartingHash}`
let classMethodsDependencyNames = new Set(
node.body
.map(member => {
if (
nodeValue.type === 'MemberExpression' &&
(nodeValue.object.type === 'ThisExpression' ||
(nodeValue.object.type === 'Identifier' &&
nodeValue.object.name === className)) &&
nodeValue.property.type === 'Identifier'
(member.type === 'MethodDefinition' ||
member.type === 'TSAbstractMethodDefinition') &&
'name' in member.key
) {
let isStaticDependency =
isMemberStatic || nodeValue.object.type === 'Identifier'
let dependencyName = getDependencyName(
nodeValue.property.name,
isStaticDependency,
)
if (!classMethodsDependencyNames.has(dependencyName)) {
dependencies.push(dependencyName)
return getDependencyName({
isPrivateHash: member.key.type === 'PrivateIdentifier',
nodeNameWithoutStartingHash: member.key.name,
isStatic: member.static,
})
}
return null
})
.filter(Boolean),
)
let extractDependencies = (expression, isMemberStatic) => {
let dependencies = []
let checkNode = nodeValue => {
if (
nodeValue.type === 'MemberExpression' &&
(nodeValue.object.type === 'ThisExpression' ||
(nodeValue.object.type === 'Identifier' &&
nodeValue.object.name === className)) &&
(nodeValue.property.type === 'Identifier' ||
nodeValue.property.type === 'PrivateIdentifier')
) {
let isStaticDependency =
isMemberStatic || nodeValue.object.type === 'Identifier'
let dependencyName = getDependencyName({
isPrivateHash: nodeValue.property.type === 'PrivateIdentifier',
nodeNameWithoutStartingHash: nodeValue.property.name,
isStatic: isStaticDependency,
})
if (!classMethodsDependencyNames.has(dependencyName)) {
dependencies.push(dependencyName)
}
}
if (nodeValue.type === 'Property') {
traverseNode(nodeValue.key)
traverseNode(nodeValue.value)
}
if (nodeValue.type === 'ConditionalExpression') {
traverseNode(nodeValue.test)
traverseNode(nodeValue.consequent)
traverseNode(nodeValue.alternate)
}
if (
'expression' in nodeValue &&
typeof nodeValue.expression !== 'boolean'
) {
traverseNode(nodeValue.expression)
}
if ('object' in nodeValue) {
traverseNode(nodeValue.object)
}
if ('callee' in nodeValue) {
traverseNode(nodeValue.callee)
}
if ('init' in nodeValue && nodeValue.init) {
traverseNode(nodeValue.init)
}
if ('body' in nodeValue && nodeValue.body) {
traverseNode(nodeValue.body)
}
if ('left' in nodeValue) {
traverseNode(nodeValue.left)
}
if ('right' in nodeValue) {
traverseNode(nodeValue.right)
}
if ('elements' in nodeValue) {
let elements = nodeValue.elements.filter(
currentNode => currentNode !== null,
)
for (let element of elements) {
traverseNode(element)
}
}
if ('argument' in nodeValue && nodeValue.argument) {
traverseNode(nodeValue.argument)
}
if ('arguments' in nodeValue) {
let shouldIgnore = false
if (nodeValue.type === 'CallExpression') {
let functionName =
'name' in nodeValue.callee ? nodeValue.callee.name : null
shouldIgnore =
functionName !== null &&
options.ignoreCallbackDependenciesPatterns.some(pattern =>
matches.matches(functionName, pattern),
)
}
if (!shouldIgnore) {
for (let argument of nodeValue.arguments) {
traverseNode(argument)
}
}
if (nodeValue.type === 'Property') {
traverseNode(nodeValue.key)
traverseNode(nodeValue.value)
}
if ('declarations' in nodeValue) {
for (let declaration of nodeValue.declarations) {
traverseNode(declaration)
}
if (nodeValue.type === 'ConditionalExpression') {
traverseNode(nodeValue.test)
traverseNode(nodeValue.consequent)
traverseNode(nodeValue.alternate)
}
if ('properties' in nodeValue) {
for (let property of nodeValue.properties) {
traverseNode(property)
}
if (
'expression' in nodeValue &&
typeof nodeValue.expression !== 'boolean'
) {
traverseNode(nodeValue.expression)
}
if ('expressions' in nodeValue) {
for (let nodeExpression of nodeValue.expressions) {
traverseNode(nodeExpression)
}
if ('object' in nodeValue) {
traverseNode(nodeValue.object)
}
}
let traverseNode = nodeValue => {
if (Array.isArray(nodeValue)) {
for (let nodeItem of nodeValue) {
traverseNode(nodeItem)
}
if ('callee' in nodeValue) {
traverseNode(nodeValue.callee)
} else {
checkNode(nodeValue)
}
}
traverseNode(expression)
return dependencies
}
let overloadSignatureGroups = sortClassesUtils.getOverloadSignatureGroups(
node.body,
)
let formattedNodes = node.body.reduce(
(accumulator, member) => {
var _a2, _b, _c, _d, _e
let name
let dependencies = []
let { defineGroup, getGroup } = useGroups.useGroups(options)
if (member.type === 'StaticBlock') {
name = 'static'
} else if (member.type === 'TSIndexSignature') {
name = sourceCode.text.slice(
member.range.at(0),
((_a2 = member.typeAnnotation) == null
? void 0
: _a2.range.at(0)) ?? member.range.at(1),
)
} else if (member.key.type === 'Identifier') {
;({ name } = member.key)
} else {
name = sourceCode.getText(member.key)
}
let isPrivateHash =
'key' in member && member.key.type === 'PrivateIdentifier'
let decorated = false
let decorators = []
if ('decorators' in member) {
decorated = member.decorators.length > 0
for (let decorator of member.decorators) {
if (decorator.expression.type === 'Identifier') {
decorators.push(decorator.expression.name)
} else if (
decorator.expression.type === 'CallExpression' &&
decorator.expression.callee.type === 'Identifier'
) {
decorators.push(decorator.expression.callee.name)
}
}
if ('init' in nodeValue && nodeValue.init) {
traverseNode(nodeValue.init)
}
let memberValue
let modifiers = []
let selectors = []
let addSafetySemicolonWhenInline = true
if (
member.type === 'MethodDefinition' ||
member.type === 'TSAbstractMethodDefinition'
) {
if (member.static) {
modifiers.push('static')
}
if ('body' in nodeValue && nodeValue.body) {
traverseNode(nodeValue.body)
if (member.type === 'TSAbstractMethodDefinition') {
modifiers.push('abstract')
} else if (!node.parent.declare) {
addSafetySemicolonWhenInline = false
}
if ('left' in nodeValue) {
traverseNode(nodeValue.left)
if (decorated) {
modifiers.push('decorated')
}
if ('right' in nodeValue) {
traverseNode(nodeValue.right)
if (member.override) {
modifiers.push('override')
}
if ('elements' in nodeValue) {
nodeValue.elements
.filter(currentNode => currentNode !== null)
.forEach(traverseNode)
if (member.accessibility === 'protected') {
modifiers.push('protected')
} else if (member.accessibility === 'private' || isPrivateHash) {
modifiers.push('private')
} else {
modifiers.push('public')
}
if ('argument' in nodeValue && nodeValue.argument) {
traverseNode(nodeValue.argument)
if (member.optional) {
modifiers.push('optional')
}
if ('arguments' in nodeValue) {
nodeValue.arguments.forEach(traverseNode)
if (member.value.async) {
modifiers.push('async')
}
if ('declarations' in nodeValue) {
nodeValue.declarations.forEach(traverseNode)
if (member.kind === 'constructor') {
selectors.push('constructor')
}
if ('properties' in nodeValue) {
nodeValue.properties.forEach(traverseNode)
if (member.kind === 'get') {
selectors.push('get-method')
}
if ('expressions' in nodeValue) {
nodeValue.expressions.forEach(traverseNode)
if (member.kind === 'set') {
selectors.push('set-method')
}
}
let traverseNode = nodeValue => {
if (Array.isArray(nodeValue)) {
nodeValue.forEach(traverseNode)
selectors.push('method')
} else if (member.type === 'TSIndexSignature') {
if (member.static) {
modifiers.push('static')
}
if (member.readonly) {
modifiers.push('readonly')
}
selectors.push('index-signature')
} else if (member.type === 'StaticBlock') {
addSafetySemicolonWhenInline = false
selectors.push('static-block')
dependencies = extractDependencies(member, true)
} else if (
member.type === 'AccessorProperty' ||
member.type === 'TSAbstractAccessorProperty'
) {
if (member.static) {
modifiers.push('static')
}
if (member.type === 'TSAbstractAccessorProperty') {
modifiers.push('abstract')
}
if (decorated) {
modifiers.push('decorated')
}
if (member.override) {
modifiers.push('override')
}
if (member.accessibility === 'protected') {
modifiers.push('protected')
} else if (member.accessibility === 'private' || isPrivateHash) {
modifiers.push('private')
} else {
checkNode(nodeValue)
modifiers.push('public')
}
}
traverseNode(expression)
return dependencies
}
let overloadSignatureGroups =
sortClassesUtils.getOverloadSignatureGroups(node.body)
let formattedNodes = node.body.reduce(
(accumulator, member) => {
var _a2, _b, _c, _d
let comments = getCommentsBefore.getCommentsBefore(
member,
sourceCode,
)
if (
options.partitionByComment &&
isPartitionComment.hasPartitionComment(
options.partitionByComment,
comments,
options.matcher,
)
) {
accumulator.push([])
selectors.push('accessor-property')
} else {
if (member.static) {
modifiers.push('static')
}
let name
let dependencies = []
let { getGroup, defineGroup, setCustomGroups } =
useGroups.useGroups(options)
if (member.type === 'StaticBlock') {
name = 'static'
} else if (member.type === 'TSIndexSignature') {
name = sourceCode.text.slice(
member.range.at(0),
((_a2 = member.typeAnnotation) == null
? void 0
: _a2.range.at(0)) ?? member.range.at(1),
)
} else if (member.key.type === 'Identifier') {
;({ name } = member.key)
if (member.declare) {
modifiers.push('declare')
}
if (member.type === 'TSAbstractPropertyDefinition') {
modifiers.push('abstract')
}
if (decorated) {
modifiers.push('decorated')
}
if (member.override) {
modifiers.push('override')
}
if (member.readonly) {
modifiers.push('readonly')
}
if (member.accessibility === 'protected') {
modifiers.push('protected')
} else if (member.accessibility === 'private' || isPrivateHash) {
modifiers.push('private')
} else {
name = sourceCode.text.slice(...member.key.range)
modifiers.push('public')
}
let isPrivateHash =
'key' in member && member.key.type === 'PrivateIdentifier'
let decorated = false
let decorators = []
if ('decorators' in member) {
decorated = member.decorators.length > 0
for (let decorator of member.decorators) {
if (decorator.expression.type === 'Identifier') {
decorators.push(decorator.expression.name)
} else if (
decorator.expression.type === 'CallExpression' &&
decorator.expression.callee.type === 'Identifier'
) {
decorators.push(decorator.expression.callee.name)
}
}
if (member.optional) {
modifiers.push('optional')
}
let memberValue
let modifiers = []
let selectors = []
if (
member.type === 'MethodDefinition' ||
member.type === 'TSAbstractMethodDefinition'
((_b = member.value) == null ? void 0 : _b.type) ===
'ArrowFunctionExpression' ||
((_c = member.value) == null ? void 0 : _c.type) ===
'FunctionExpression'
) {
if (member.static) {
modifiers.push('static')
if (member.value.async) {
modifiers.push('async')
}
if (member.type === 'TSAbstractMethodDefinition') {
modifiers.push('abstract')
}
if (decorated) {
modifiers.push('decorated')
}
if (member.override) {
modifiers.push('override')
}
if (member.accessibility === 'protected') {
modifiers.push('protected')
} else if (member.accessibility === 'private' || isPrivateHash) {
modifiers.push('private')
} else {
modifiers.push('public')
}
if (member.optional) {
modifiers.push('optional')
}
if (member.kind === 'constructor') {
selectors.push('constructor')
}
if (member.kind === 'get') {
selectors.push('get-method')
}
if (member.kind === 'set') {
selectors.push('set-method')
}
selectors.push('method')
} else if (member.type === 'TSIndexSignature') {
if (member.static) {
modifiers.push('static')
}
if (member.readonly) {
modifiers.push('readonly')
}
selectors.push('index-signature')
} else if (member.type === 'StaticBlock') {
selectors.push('static-block')
dependencies = extractDependencies(member, true)
} else if (
member.type === 'AccessorProperty' ||
member.type === 'TSAbstractAccessorProperty'
) {
if (member.static) {
modifiers.push('static')
}
if (member.type === 'TSAbstractAccessorProperty') {
modifiers.push('abstract')
}
if (decorated) {
modifiers.push('decorated')
}
if (member.override) {
modifiers.push('override')
}
if (member.accessibility === 'protected') {
modifiers.push('protected')
} else if (member.accessibility === 'private' || isPrivateHash) {
modifiers.push('private')
} else {
modifiers.push('public')
}
selectors.push('accessor-property')
} else {
if (member.static) {
modifiers.push('static')
}
if (member.declare) {
modifiers.push('declare')
}
if (member.type === 'TSAbstractPropertyDefinition') {
modifiers.push('abstract')
}
if (decorated) {
modifiers.push('decorated')
}
if (member.override) {
modifiers.push('override')
}
if (member.readonly) {
modifiers.push('readonly')
}
if (member.accessibility === 'protected') {
modifiers.push('protected')
} else if (member.accessibility === 'private' || isPrivateHash) {
modifiers.push('private')
} else {
modifiers.push('public')
}
if (member.optional) {
modifiers.push('optional')
}
let isFunctionProperty =
((_b = member.value) == null ? void 0 : _b.type) ===
'ArrowFunctionExpression' ||
((_c = member.value) == null ? void 0 : _c.type) ===
'FunctionExpression'
if (isFunctionProperty) {
selectors.push('function-property')
}
if (!isFunctionProperty && member.value) {
memberValue = sourceCode.getText(member.value)
}
selectors.push('property')
if (
member.type === 'PropertyDefinition' &&
member.value &&
!isFunctionProperty
) {
dependencies = extractDependencies(member.value, member.static)
}
selectors.push('function-property')
} else if (member.value) {
memberValue = sourceCode.getText(member.value)
dependencies = extractDependencies(member.value, member.static)
}
for (let officialGroup of sortClassesUtils.generateOfficialGroups(
selectors.push('property')
}
for (let officialGroup of generatePredefinedGroups.generatePredefinedGroups(
{
cache: cachedGroupsByModifiersAndSelectors,
selectors,
modifiers,
selectors,
)) {
defineGroup(officialGroup)
}
if (Array.isArray(options.customGroups)) {
for (let customGroup of options.customGroups) {
if (
sortClassesUtils.customGroupMatches({
customGroup,
elementName: name,
elementValue: memberValue,
modifiers,
selectors,
decorators,
matcher: options.matcher,
})
) {
defineGroup(customGroup.groupName, true)
if (getGroup() === customGroup.groupName) {
break
}
}
},
)) {
defineGroup(officialGroup)
}
for (let customGroup of options.customGroups) {
if (
sortClassesUtils.customGroupMatches({
elementValue: memberValue,
elementName: name,
customGroup,
decorators,
modifiers,
selectors,
})
) {
defineGroup(customGroup.groupName, true)
if (getGroup() === customGroup.groupName) {
break
}
} else {
setCustomGroups(options.customGroups, name, {
override: true,
})
}
let overloadSignatureGroupMember =
(_d = overloadSignatureGroups.find(overloadSignatures =>
overloadSignatures.includes(member),
)) == null
? void 0
: _d.at(-1)
let sortingNode = {
size: overloadSignatureGroupMember
? rangeToDiff.rangeToDiff(overloadSignatureGroupMember.range)
: rangeToDiff.rangeToDiff(member.range),
group: getGroup(),
node: member,
dependencies,
name,
dependencyName: getDependencyName(
name,
modifiers.includes('static'),
),
}
accumulator.at(-1).push(sortingNode)
return accumulator
},
[[]],
)
let sortedNodes = formattedNodes
.map(nodes2 =>
}
let overloadSignatureGroupMember =
(_d = overloadSignatureGroups.find(overloadSignatures =>
overloadSignatures.includes(member),
)) == null
? void 0
: _d.at(-1)
let sortingNode = {
dependencyName: getDependencyName({
nodeNameWithoutStartingHash: name.startsWith('#')
? name.slice(1)
: name,
isStatic: modifiers.includes('static'),
isPrivateHash,
}),
size: overloadSignatureGroupMember
? rangeToDiff.rangeToDiff(
overloadSignatureGroupMember,
sourceCode,
)
: rangeToDiff.rangeToDiff(member, sourceCode),
isEslintDisabled: isNodeEslintDisabled.isNodeEslintDisabled(
member,
eslintDisabledLines,
),
addSafetySemicolonWhenInline,
group: getGroup(),
node: member,
dependencies,
name,
}
let comments = getCommentsBefore.getCommentsBefore(member, sourceCode)
let lastMember =
(_e = accumulator.at(-1)) == null ? void 0 : _e.at(-1)
if (
(options.partitionByNewLine &&
lastMember &&
getLinesBetween.getLinesBetween(
sourceCode,
lastMember,
sortingNode,
)) ||
(options.partitionByComment &&
isPartitionComment.hasPartitionComment(
options.partitionByComment,
comments,
))
) {
accumulator.push([])
}
accumulator.at(-1).push(sortingNode)
return accumulator
},
[[]],
)
let sortNodesIgnoringEslintDisabledNodes = ignoreEslintDisabledNodes =>
sortNodesByDependencies.sortNodesByDependencies(
formattedNodes.flatMap(nodes2 =>
sortNodesByGroups.sortNodesByGroups(nodes2, options, {
getGroupCompareOptions: groupNumber =>
sortClassesUtils.getCompareOptions(options, groupNumber),
isNodeIgnored: sortingNode =>
getGroupNumber.getGroupNumber(options.groups, sortingNode) ===
options.groups.length,
getGroupCompareOptions: groupNumber =>
sortClassesUtils.getCompareOptions(options, groupNumber),
ignoreEslintDisabledNodes,
}),
)
.flat()
sortedNodes =
sortNodesByDependencies.sortNodesByDependencies(sortedNodes)
let nodes = formattedNodes.flat()
pairwise.pairwise(nodes, (left, right) => {
let leftNum = getGroupNumber.getGroupNumber(options.groups, left)
let rightNum = getGroupNumber.getGroupNumber(options.groups, right)
let indexOfLeft = sortedNodes.indexOf(left)
let indexOfRight = sortedNodes.indexOf(right)
let firstUnorderedNodeDependentOnRight =
sortNodesByDependencies.getFirstUnorderedNodeDependentOn(
right,
nodes,
),
{
ignoreEslintDisabledNodes,
},
)
let sortedNodes = sortNodesIgnoringEslintDisabledNodes(false)
let sortedNodesExcludingEslintDisabled =
sortNodesIgnoringEslintDisabledNodes(true)
let nodes = formattedNodes.flat()
pairwise.pairwise(nodes, (left, right) => {
let leftNumber = getGroupNumber.getGroupNumber(options.groups, left)
let rightNumber = getGroupNumber.getGroupNumber(options.groups, right)
let indexOfLeft = sortedNodes.indexOf(left)
let indexOfRight = sortedNodes.indexOf(right)
let indexOfRightExcludingEslintDisabled =
sortedNodesExcludingEslintDisabled.indexOf(right)
let messageIds = []
let firstUnorderedNodeDependentOnRight =
sortNodesByDependencies.getFirstUnorderedNodeDependentOn(right, nodes)
if (
firstUnorderedNodeDependentOnRight ||
indexOfLeft > indexOfRight ||
indexOfLeft >= indexOfRightExcludingEslintDisabled
) {
if (firstUnorderedNodeDependentOnRight) {
messageIds.push('unexpectedClassesDependencyOrder')
} else {
messageIds.push(
leftNumber === rightNumber
? 'unexpectedClassesOrder'
: 'unexpectedClassesGroupOrder',
)
if (
firstUnorderedNodeDependentOnRight ||
indexOfLeft > indexOfRight
) {
let messageId
if (firstUnorderedNodeDependentOnRight) {
messageId = 'unexpectedClassesDependencyOrder'
} else {
messageId =
leftNum !== rightNum
? 'unexpectedClassesGroupOrder'
: 'unexpectedClassesOrder'
}
context.report({
messageId,
data: {
left: toSingleLine.toSingleLine(left.name),
leftGroup: left.group,
right: toSingleLine.toSingleLine(right.name),
rightGroup: right.group,
nodeDependentOnRight:
firstUnorderedNodeDependentOnRight == null
? void 0
: firstUnorderedNodeDependentOnRight.name,
},
node: right.node,
fix: fixer =>
makeFixes.makeFixes(
fixer,
nodes,
sortedNodes,
sourceCode,
options,
),
})
}
})
}
}
messageIds = [
...messageIds,
...getNewlinesErrors.getNewlinesErrors({
missedSpacingError: 'missedSpacingBetweenClassMembers',
extraSpacingError: 'extraSpacingBetweenClassMembers',
rightNum: rightNumber,
leftNum: leftNumber,
sourceCode,
options,
right,
left,
}),
]
for (let messageId of messageIds) {
context.report({
fix: fixer => [
...makeFixes.makeFixes({
sortedNodes: sortedNodesExcludingEslintDisabled,
sourceCode,
options,
fixer,
nodes,
}),
...makeNewlinesFixes.makeNewlinesFixes({
sortedNodes: sortedNodesExcludingEslintDisabled,
sourceCode,
options,
fixer,
nodes,
}),
],
data: {
nodeDependentOnRight:
firstUnorderedNodeDependentOnRight == null
? void 0
: firstUnorderedNodeDependentOnRight.name,
right: toSingleLine.toSingleLine(right.name),
left: toSingleLine.toSingleLine(left.name),
rightGroup: right.group,
leftGroup: left.group,
},
node: right.node,
messageId,
})
}
})
},
}),
meta: {
schema: [
{
properties: {
customGroups: {
items: {
oneOf: [
{
properties: {
...sortClasses_types.customGroupNameJsonSchema,
...sortClasses_types.customGroupSortJsonSchema,
anyOf: {
items: {
properties: {
...sortClasses_types.singleCustomGroupJsonSchema,
},
description: 'Custom group.',
additionalProperties: false,
type: 'object',
},
type: 'array',
},
},
description: 'Custom group block.',
additionalProperties: false,
type: 'object',
},
{
properties: {
...sortClasses_types.customGroupNameJsonSchema,
...sortClasses_types.customGroupSortJsonSchema,
...sortClasses_types.singleCustomGroupJsonSchema,
},
description: 'Custom group.',
additionalProperties: false,
type: 'object',
},
],
},
description: 'Specifies custom groups.',
type: 'array',
},
ignoreCallbackDependenciesPatterns: {
description:
'Patterns that should be ignored when detecting dependencies in method callbacks.',
items: {
type: 'string',
},
type: 'array',
},
partitionByComment: {
...commonJsonSchemas.partitionByCommentJsonSchema,
description:
'Allows to use comments to separate the class members into logical groups.',
},
partitionByNewLine: commonJsonSchemas.partitionByNewLineJsonSchema,
specialCharacters: commonJsonSchemas.specialCharactersJsonSchema,
newlinesBetween: commonJsonSchemas.newlinesBetweenJsonSchema,
ignoreCase: commonJsonSchemas.ignoreCaseJsonSchema,
locales: commonJsonSchemas.localesJsonSchema,
groups: commonJsonSchemas.groupsJsonSchema,
order: commonJsonSchemas.orderJsonSchema,
type: commonJsonSchemas.typeJsonSchema,
},
additionalProperties: false,
type: 'object',
},
],
messages: {
unexpectedClassesGroupOrder:
'Expected "{{right}}" ({{rightGroup}}) to come before "{{left}}" ({{leftGroup}}).',
unexpectedClassesDependencyOrder:
'Expected dependency "{{right}}" to come before "{{nodeDependentOnRight}}".',
missedSpacingBetweenClassMembers:
'Missed spacing between "{{left}}" and "{{right}}" objects.',
extraSpacingBetweenClassMembers:
'Extra spacing between "{{left}}" and "{{right}}" objects.',
unexpectedClassesOrder: 'Expected "{{right}}" to come before "{{left}}".',
},
docs: {
url: 'https://perfectionist.dev/rules/sort-classes',
description: 'Enforce sorted classes.',
recommended: true,
},
type: 'suggestion',
fixable: 'code',
},
defaultOptions: [defaultOptions],
name: 'sort-classes',
})
module.exports = sortClasses
'use strict'
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
const allSelectors = [
let allSelectors = [
'accessor-property',

@@ -14,3 +14,4 @@ 'index-signature',

]
const allModifiers = [
let allModifiers = [
'async',
'protected',

@@ -27,15 +28,15 @@ 'private',

]
const customGroupSortJsonSchema = {
let customGroupSortJsonSchema = {
type: {
enum: ['alphabetical', 'line-length', 'natural', 'unsorted'],
description: 'Custom group sort type.',
type: 'string',
enum: ['alphabetical', 'line-length', 'natural', 'unsorted'],
},
order: {
description: 'Custom group sort order.',
enum: ['desc', 'asc'],
type: 'string',
enum: ['desc', 'asc'],
},
}
const customGroupNameJsonSchema = {
let customGroupNameJsonSchema = {
groupName: {

@@ -46,20 +47,11 @@ description: 'Custom group name.',

}
const singleCustomGroupJsonSchema = {
selector: {
description: 'Selector filter.',
type: 'string',
enum: allSelectors,
},
let singleCustomGroupJsonSchema = {
modifiers: {
description: 'Modifier filters.',
type: 'array',
items: {
enum: allModifiers,
type: 'string',
enum: allModifiers,
},
description: 'Modifier filters.',
type: 'array',
},
elementNamePattern: {
description: 'Element name pattern filter.',
type: 'string',
},
elementValuePattern: {

@@ -73,2 +65,11 @@ description: 'Element value pattern filter for properties.',

},
selector: {
description: 'Selector filter.',
enum: allSelectors,
type: 'string',
},
elementNamePattern: {
description: 'Element name pattern filter.',
type: 'string',
},
}

@@ -75,0 +76,0 @@ exports.allModifiers = allModifiers

'use strict'
const commonJsonSchemas = require('../utils/common-json-schemas.js')
const sortNodesByDependencies = require('../utils/sort-nodes-by-dependencies.js')
const getEslintDisabledLines = require('../utils/get-eslint-disabled-lines.js')
const isNodeEslintDisabled = require('../utils/is-node-eslint-disabled.js')
const isPartitionComment = require('../utils/is-partition-comment.js')

@@ -11,2 +14,3 @@ const getCommentsBefore = require('../utils/get-comments-before.js')

const getSettings = require('../utils/get-settings.js')
const isSortable = require('../utils/is-sortable.js')
const sortNodes = require('../utils/sort-nodes.js')

@@ -16,41 +20,210 @@ const makeFixes = require('../utils/make-fixes.js')

const pairwise = require('../utils/pairwise.js')
let defaultOptions = {
partitionByComment: false,
partitionByNewLine: false,
specialCharacters: 'keep',
forceNumericSort: false,
type: 'alphabetical',
sortByValue: false,
ignoreCase: true,
locales: 'en-US',
order: 'asc',
}
const sortEnums = createEslintRule.createEslintRule({
name: 'sort-enums',
create: context => ({
TSEnumDeclaration: node => {
let members = node.body.members ?? node.members ?? []
if (
!isSortable.isSortable(members) ||
!members.every(({ initializer }) => initializer)
) {
return
}
let settings = getSettings.getSettings(context.settings)
let options = complete.complete(
context.options.at(0),
settings,
defaultOptions,
)
let sourceCode = getSourceCode.getSourceCode(context)
let eslintDisabledLines = getEslintDisabledLines.getEslintDisabledLines({
ruleName: context.id,
sourceCode,
})
let extractDependencies = (expression, enumName) => {
let dependencies = []
let checkNode = nodeValue => {
if (
nodeValue.type === 'MemberExpression' &&
nodeValue.object.type === 'Identifier' &&
nodeValue.object.name === enumName &&
nodeValue.property.type === 'Identifier'
) {
dependencies.push(nodeValue.property.name)
} else if (nodeValue.type === 'Identifier') {
dependencies.push(nodeValue.name)
}
if ('left' in nodeValue) {
checkNode(nodeValue.left)
}
if ('right' in nodeValue) {
checkNode(nodeValue.right)
}
if ('expressions' in nodeValue) {
for (let currentExpression of nodeValue.expressions) {
checkNode(currentExpression)
}
}
}
checkNode(expression)
return dependencies
}
let formattedMembers = members.reduce(
(accumulator, member) => {
var _a
let dependencies = []
if (member.initializer) {
dependencies = extractDependencies(member.initializer, node.id.name)
}
let lastSortingNode =
(_a = accumulator.at(-1)) == null ? void 0 : _a.at(-1)
let sortingNode = {
name:
member.id.type === 'Literal'
? `${member.id.value}`
: sourceCode.getText(member.id),
isEslintDisabled: isNodeEslintDisabled.isNodeEslintDisabled(
member,
eslintDisabledLines,
),
size: rangeToDiff.rangeToDiff(member, sourceCode),
node: member,
dependencies,
}
if (
(options.partitionByComment &&
isPartitionComment.hasPartitionComment(
options.partitionByComment,
getCommentsBefore.getCommentsBefore(member, sourceCode),
)) ||
(options.partitionByNewLine &&
lastSortingNode &&
getLinesBetween.getLinesBetween(
sourceCode,
lastSortingNode,
sortingNode,
))
) {
accumulator.push([])
}
accumulator.at(-1).push(sortingNode)
return accumulator
},
[[]],
)
let isNumericEnum = members.every(member => {
var _a
return (
((_a = member.initializer) == null ? void 0 : _a.type) ===
'Literal' && typeof member.initializer.value === 'number'
)
})
let compareOptions = {
// Get the enum value rather than the name if needed
nodeValueGetter:
options.sortByValue || (isNumericEnum && options.forceNumericSort)
? sortingNode => {
var _a, _b
if (
sortingNode.node.type === 'TSEnumMember' &&
((_a = sortingNode.node.initializer) == null
? void 0
: _a.type) === 'Literal'
) {
return (
((_b = sortingNode.node.initializer.value) == null
? void 0
: _b.toString()) ?? ''
)
}
return ''
}
: null,
// If the enum is numeric, and we sort by value, always use the `natural` sort type, which will correctly sort them.
type:
isNumericEnum && (options.forceNumericSort || options.sortByValue)
? 'natural'
: options.type,
specialCharacters: options.specialCharacters,
ignoreCase: options.ignoreCase,
locales: options.locales,
order: options.order,
}
let sortNodesIgnoringEslintDisabledNodes = ignoreEslintDisabledNodes =>
sortNodesByDependencies.sortNodesByDependencies(
formattedMembers.flatMap(nodes2 =>
sortNodes.sortNodes(nodes2, compareOptions, {
ignoreEslintDisabledNodes,
}),
),
{
ignoreEslintDisabledNodes,
},
)
let sortedNodes = sortNodesIgnoringEslintDisabledNodes(false)
let sortedNodesExcludingEslintDisabled =
sortNodesIgnoringEslintDisabledNodes(true)
let nodes = formattedMembers.flat()
pairwise.pairwise(nodes, (left, right) => {
let indexOfLeft = sortedNodes.indexOf(left)
let indexOfRight = sortedNodes.indexOf(right)
let indexOfRightExcludingEslintDisabled =
sortedNodesExcludingEslintDisabled.indexOf(right)
if (
indexOfLeft < indexOfRight &&
indexOfLeft < indexOfRightExcludingEslintDisabled
) {
return
}
let firstUnorderedNodeDependentOnRight =
sortNodesByDependencies.getFirstUnorderedNodeDependentOn(right, nodes)
context.report({
fix: fixer =>
makeFixes.makeFixes({
sortedNodes: sortedNodesExcludingEslintDisabled,
sourceCode,
options,
fixer,
nodes,
}),
data: {
nodeDependentOnRight:
firstUnorderedNodeDependentOnRight == null
? void 0
: firstUnorderedNodeDependentOnRight.name,
right: toSingleLine.toSingleLine(right.name),
left: toSingleLine.toSingleLine(left.name),
},
messageId: firstUnorderedNodeDependentOnRight
? 'unexpectedEnumsDependencyOrder'
: 'unexpectedEnumsOrder',
node: right.node,
})
})
},
}),
meta: {
type: 'suggestion',
docs: {
description: 'Enforce sorted TypeScript enums.',
},
fixable: 'code',
schema: [
{
type: 'object',
properties: {
type: {
description: 'Specifies the sorting method.',
type: 'string',
enum: ['alphabetical', 'natural', 'line-length'],
},
order: {
partitionByComment: {
...commonJsonSchemas.partitionByCommentJsonSchema,
description:
'Determines whether the sorted items should be in ascending or descending order.',
type: 'string',
enum: ['asc', 'desc'],
'Allows you to use comments to separate the members of enums into logical groups.',
},
matcher: {
description: 'Specifies the string matcher.',
type: 'string',
enum: ['minimatch', 'regex'],
},
ignoreCase: {
forceNumericSort: {
description:
'Controls whether sorting should be case-sensitive or not.',
'Will always sort numeric enums by their value regardless of the sort type specified.',
type: 'boolean',
},
specialCharacters: {
description:
'Controls how special characters should be handled before sorting.',
type: 'string',
enum: ['remove', 'trim', 'keep'],
},
sortByValue: {

@@ -60,233 +233,29 @@ description: 'Compare enum values instead of names.',

},
forceNumericSort: {
description:
'Will always sort numeric enums by their value regardless of the sort type specified.',
type: 'boolean',
},
partitionByComment: {
description:
'Allows you to use comments to separate the members of enums into logical groups.',
anyOf: [
{
type: 'array',
items: {
type: 'string',
},
},
{
type: 'boolean',
},
{
type: 'string',
},
],
},
partitionByNewLine: {
description:
'Allows to use spaces to separate the nodes into logical groups.',
type: 'boolean',
},
partitionByNewLine: commonJsonSchemas.partitionByNewLineJsonSchema,
specialCharacters: commonJsonSchemas.specialCharactersJsonSchema,
ignoreCase: commonJsonSchemas.ignoreCaseJsonSchema,
locales: commonJsonSchemas.localesJsonSchema,
order: commonJsonSchemas.orderJsonSchema,
type: commonJsonSchemas.typeJsonSchema,
},
additionalProperties: false,
type: 'object',
},
],
messages: {
unexpectedEnumsOrder: 'Expected "{{right}}" to come before "{{left}}".',
unexpectedEnumsDependencyOrder:
'Expected dependency "{{right}}" to come before "{{nodeDependentOnRight}}".',
unexpectedEnumsOrder: 'Expected "{{right}}" to come before "{{left}}".',
},
docs: {
url: 'https://perfectionist.dev/rules/sort-enums',
description: 'Enforce sorted TypeScript enums.',
recommended: true,
},
type: 'suggestion',
fixable: 'code',
},
defaultOptions: [
{
type: 'alphabetical',
order: 'asc',
ignoreCase: true,
specialCharacters: 'keep',
matcher: 'minimatch',
sortByValue: false,
partitionByComment: false,
partitionByNewLine: false,
forceNumericSort: false,
},
],
create: context => ({
TSEnumDeclaration: node => {
let getMembers = nodeValue => {
var _a
return (
/* v8 ignore next 2 */
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
((_a = node.body) == null ? void 0 : _a.members) ??
nodeValue.members ??
[]
)
}
let members = getMembers(node)
if (
members.length > 1 &&
members.every(({ initializer }) => initializer)
) {
let settings = getSettings.getSettings(context.settings)
let options = complete.complete(context.options.at(0), settings, {
partitionByComment: false,
partitionByNewLine: false,
type: 'alphabetical',
matcher: 'minimatch',
ignoreCase: true,
specialCharacters: 'keep',
order: 'asc',
sortByValue: false,
forceNumericSort: false,
})
let sourceCode = getSourceCode.getSourceCode(context)
let partitionComment = options.partitionByComment
let extractDependencies = (expression, enumName) => {
let dependencies = []
let checkNode = nodeValue => {
if (
nodeValue.type === 'MemberExpression' &&
nodeValue.object.type === 'Identifier' &&
nodeValue.object.name === enumName &&
nodeValue.property.type === 'Identifier'
) {
dependencies.push(nodeValue.property.name)
} else if (nodeValue.type === 'Identifier') {
dependencies.push(nodeValue.name)
}
if ('left' in nodeValue) {
checkNode(nodeValue.left)
}
if ('right' in nodeValue) {
checkNode(nodeValue.right)
}
if ('expressions' in nodeValue) {
nodeValue.expressions.forEach(checkNode)
}
}
checkNode(expression)
return dependencies
}
let formattedMembers = members.reduce(
(accumulator, member) => {
var _a
let dependencies = []
if (member.initializer) {
dependencies = extractDependencies(
member.initializer,
node.id.name,
)
}
let lastSortingNode =
(_a = accumulator.at(-1)) == null ? void 0 : _a.at(-1)
let sortingNode = {
size: rangeToDiff.rangeToDiff(member.range),
node: member,
dependencies,
name:
member.id.type === 'Literal'
? `${member.id.value}`
: `${sourceCode.text.slice(...member.id.range)}`,
}
if (
(partitionComment &&
isPartitionComment.hasPartitionComment(
partitionComment,
getCommentsBefore.getCommentsBefore(member, sourceCode),
options.matcher,
)) ||
(options.partitionByNewLine &&
lastSortingNode &&
getLinesBetween.getLinesBetween(
sourceCode,
lastSortingNode,
sortingNode,
))
) {
accumulator.push([])
}
accumulator.at(-1).push(sortingNode)
return accumulator
},
[[]],
)
let isNumericEnum = members.every(member => {
var _a
return (
((_a = member.initializer) == null ? void 0 : _a.type) ===
'Literal' && typeof member.initializer.value === 'number'
)
})
let compareOptions = {
// If the enum is numeric, and we sort by value, always use the `natural` sort type, which will correctly sort them.
type:
isNumericEnum && (options.forceNumericSort || options.sortByValue)
? 'natural'
: options.type,
order: options.order,
ignoreCase: options.ignoreCase,
specialCharacters: options.specialCharacters,
// Get the enum value rather than the name if needed
nodeValueGetter:
options.sortByValue || (isNumericEnum && options.forceNumericSort)
? sortingNode => {
var _a, _b
if (
sortingNode.node.type === 'TSEnumMember' &&
((_a = sortingNode.node.initializer) == null
? void 0
: _a.type) === 'Literal'
) {
return (
((_b = sortingNode.node.initializer.value) == null
? void 0
: _b.toString()) ?? ''
)
}
return ''
}
: void 0,
}
let sortedNodes = sortNodesByDependencies.sortNodesByDependencies(
formattedMembers
.map(nodes2 => sortNodes.sortNodes(nodes2, compareOptions))
.flat(),
)
let nodes = formattedMembers.flat()
pairwise.pairwise(nodes, (left, right) => {
let indexOfLeft = sortedNodes.indexOf(left)
let indexOfRight = sortedNodes.indexOf(right)
if (indexOfLeft > indexOfRight) {
let firstUnorderedNodeDependentOnRight =
sortNodesByDependencies.getFirstUnorderedNodeDependentOn(
right,
nodes,
)
context.report({
messageId: firstUnorderedNodeDependentOnRight
? 'unexpectedEnumsDependencyOrder'
: 'unexpectedEnumsOrder',
data: {
left: toSingleLine.toSingleLine(left.name),
right: toSingleLine.toSingleLine(right.name),
nodeDependentOnRight:
firstUnorderedNodeDependentOnRight == null
? void 0
: firstUnorderedNodeDependentOnRight.name,
},
node: right.node,
fix: fixer =>
makeFixes.makeFixes(
fixer,
nodes,
sortedNodes,
sourceCode,
options,
),
})
}
})
}
},
}),
defaultOptions: [defaultOptions],
name: 'sort-enums',
})
module.exports = sortEnums
'use strict'
const commonJsonSchemas = require('../utils/common-json-schemas.js')
const getEslintDisabledLines = require('../utils/get-eslint-disabled-lines.js')
const isNodeEslintDisabled = require('../utils/is-node-eslint-disabled.js')
const isPartitionComment = require('../utils/is-partition-comment.js')

@@ -13,103 +16,26 @@ const getCommentsBefore = require('../utils/get-comments-before.js')

const pairwise = require('../utils/pairwise.js')
let defaultOptions = {
specialCharacters: 'keep',
partitionByComment: false,
partitionByNewLine: false,
type: 'alphabetical',
groupKind: 'mixed',
ignoreCase: true,
locales: 'en-US',
order: 'asc',
}
const sortExports = createEslintRule.createEslintRule({
name: 'sort-exports',
meta: {
type: 'suggestion',
docs: {
description: 'Enforce sorted exports.',
},
fixable: 'code',
schema: [
{
type: 'object',
properties: {
type: {
description: 'Specifies the sorting method.',
type: 'string',
enum: ['alphabetical', 'natural', 'line-length'],
},
order: {
description:
'Determines whether the sorted items should be in ascending or descending order.',
type: 'string',
enum: ['asc', 'desc'],
},
matcher: {
description: 'Specifies the string matcher.',
type: 'string',
enum: ['minimatch', 'regex'],
},
ignoreCase: {
description:
'Controls whether sorting should be case-sensitive or not.',
type: 'boolean',
},
specialCharacters: {
description:
'Controls how special characters should be handled before sorting.',
type: 'string',
enum: ['remove', 'trim', 'keep'],
},
partitionByComment: {
description:
'Allows you to use comments to separate the exports into logical groups.',
anyOf: [
{
type: 'array',
items: {
type: 'string',
},
},
{
type: 'boolean',
},
{
type: 'string',
},
],
},
partitionByNewLine: {
description:
'Allows to use spaces to separate the nodes into logical groups.',
type: 'boolean',
},
groupKind: {
description: 'Specifies top-level groups.',
type: 'string',
enum: ['mixed', 'values-first', 'types-first'],
},
},
additionalProperties: false,
},
],
messages: {
unexpectedExportsOrder: 'Expected "{{right}}" to come before "{{left}}".',
},
},
defaultOptions: [
{
type: 'alphabetical',
order: 'asc',
ignoreCase: true,
specialCharacters: 'keep',
matcher: 'minimatch',
partitionByComment: false,
partitionByNewLine: false,
groupKind: 'mixed',
},
],
create: context => {
let settings = getSettings.getSettings(context.settings)
let options = complete.complete(context.options.at(0), settings, {
type: 'alphabetical',
ignoreCase: true,
specialCharacters: 'keep',
order: 'asc',
matcher: 'minimatch',
partitionByComment: false,
partitionByNewLine: false,
groupKind: 'mixed',
})
let options = complete.complete(
context.options.at(0),
settings,
defaultOptions,
)
let sourceCode = getSourceCode.getSourceCode(context)
let partitionComment = options.partitionByComment
let eslintDisabledLines = getEslintDisabledLines.getEslintDisabledLines({
ruleName: context.id,
sourceCode,
})
let parts = [[]]

@@ -119,3 +45,9 @@ let registerNode = node => {

let sortingNode = {
size: rangeToDiff.rangeToDiff(node.range),
isEslintDisabled: isNodeEslintDisabled.isNodeEslintDisabled(
node,
eslintDisabledLines,
),
groupKind: node.exportKind === 'value' ? 'value' : 'type',
size: rangeToDiff.rangeToDiff(node, sourceCode),
addSafetySemicolonWhenInline: true,
name: node.source.value,

@@ -130,3 +62,2 @@ node,

getCommentsBefore.getCommentsBefore(node, sourceCode),
options.matcher,
)) ||

@@ -142,63 +73,104 @@ (options.partitionByNewLine &&

return {
ExportAllDeclaration: registerNode,
ExportNamedDeclaration: node => {
if (node.source !== null) {
registerNode(node)
'Program:exit': () => {
let groupKindOrder
if (options.groupKind === 'values-first') {
groupKindOrder = ['value', 'type']
} else if (options.groupKind === 'types-first') {
groupKindOrder = ['type', 'value']
} else {
groupKindOrder = ['any']
}
},
'Program:exit': () => {
for (let nodes of parts) {
let groupedByKind
if (options.groupKind !== 'mixed') {
groupedByKind = nodes.reduce(
(accumulator, currentNode) => {
let exportTypeIndex =
options.groupKind === 'types-first' ? 0 : 1
let exportIndex = options.groupKind === 'types-first' ? 1 : 0
if (currentNode.node.exportKind === 'value') {
accumulator[exportIndex].push(currentNode)
} else {
accumulator[exportTypeIndex].push(currentNode)
}
return accumulator
},
[[], []],
let filteredGroupKindNodes = groupKindOrder.map(groupKind =>
nodes.filter(
currentNode =>
groupKind === 'any' || currentNode.groupKind === groupKind,
),
)
let sortNodesExcludingEslintDisabled = ignoreEslintDisabledNodes =>
filteredGroupKindNodes.flatMap(groupedNodes =>
sortNodes.sortNodes(groupedNodes, options, {
ignoreEslintDisabledNodes,
}),
)
} else {
groupedByKind = [nodes]
}
let sortedNodes = []
for (let nodesByKind of groupedByKind) {
sortedNodes = [
...sortedNodes,
...sortNodes.sortNodes(nodesByKind, options),
]
}
let sortedNodes = sortNodesExcludingEslintDisabled(false)
let sortedNodesExcludingEslintDisabled =
sortNodesExcludingEslintDisabled(true)
pairwise.pairwise(nodes, (left, right) => {
let indexOfLeft = sortedNodes.indexOf(left)
let indexOfRight = sortedNodes.indexOf(right)
if (indexOfLeft > indexOfRight) {
context.report({
messageId: 'unexpectedExportsOrder',
data: {
left: left.name,
right: right.name,
},
node: right.node,
fix: fixer =>
makeFixes.makeFixes(
fixer,
nodes,
sortedNodes,
sourceCode,
options,
),
})
let indexOfRightExcludingEslintDisabled =
sortedNodesExcludingEslintDisabled.indexOf(right)
if (
indexOfLeft < indexOfRight &&
indexOfLeft < indexOfRightExcludingEslintDisabled
) {
return
}
context.report({
fix: fixer =>
makeFixes.makeFixes({
sortedNodes: sortedNodesExcludingEslintDisabled,
sourceCode,
options,
fixer,
nodes,
}),
data: {
right: right.name,
left: left.name,
},
messageId: 'unexpectedExportsOrder',
node: right.node,
})
})
}
},
ExportNamedDeclaration: node => {
if (node.source !== null) {
registerNode(node)
}
},
ExportAllDeclaration: registerNode,
}
},
meta: {
schema: [
{
properties: {
partitionByComment: {
...commonJsonSchemas.partitionByCommentJsonSchema,
description:
'Allows you to use comments to separate the exports into logical groups.',
},
groupKind: {
enum: ['mixed', 'values-first', 'types-first'],
description: 'Specifies top-level groups.',
type: 'string',
},
partitionByNewLine: commonJsonSchemas.partitionByNewLineJsonSchema,
specialCharacters: commonJsonSchemas.specialCharactersJsonSchema,
ignoreCase: commonJsonSchemas.ignoreCaseJsonSchema,
locales: commonJsonSchemas.localesJsonSchema,
order: commonJsonSchemas.orderJsonSchema,
type: commonJsonSchemas.typeJsonSchema,
},
additionalProperties: false,
type: 'object',
},
],
docs: {
url: 'https://perfectionist.dev/rules/sort-exports',
description: 'Enforce sorted exports.',
recommended: true,
},
messages: {
unexpectedExportsOrder: 'Expected "{{right}}" to come before "{{left}}".',
},
type: 'suggestion',
fixable: 'code',
},
defaultOptions: [defaultOptions],
name: 'sort-exports',
})
module.exports = sortExports
'use strict'
const node_module = require('node:module')
const commonJsonSchemas = require('../utils/common-json-schemas.js')
const validateNewlinesAndPartitionConfiguration = require('../utils/validate-newlines-and-partition-configuration.js')
const validateGroupsConfiguration = require('../utils/validate-groups-configuration.js')
const readClosestTsConfigByPath = require('../utils/read-closest-ts-config-by-path.js')
const getOptionsWithCleanGroups = require('../utils/get-options-with-clean-groups.js')
const getEslintDisabledLines = require('../utils/get-eslint-disabled-lines.js')
const isNodeEslintDisabled = require('../utils/is-node-eslint-disabled.js')
const getTypescriptImport = require('../utils/get-typescript-import.js')
const isPartitionComment = require('../utils/is-partition-comment.js')
const sortNodesByGroups = require('../utils/sort-nodes-by-groups.js')
const getCommentsBefore = require('../utils/get-comments-before.js')
const makeNewlinesFixes = require('../utils/make-newlines-fixes.js')
const getNewlinesErrors = require('../utils/get-newlines-errors.js')
const createEslintRule = require('../utils/create-eslint-rule.js')

@@ -11,5 +20,5 @@ const getLinesBetween = require('../utils/get-lines-between.js')

const getSourceCode = require('../utils/get-source-code.js')
const getNodeRange = require('../utils/get-node-range.js')
const rangeToDiff = require('../utils/range-to-diff.js')
const getSettings = require('../utils/get-settings.js')
const isSortable = require('../utils/is-sortable.js')
const useGroups = require('../utils/use-groups.js')

@@ -21,170 +30,2 @@ const makeFixes = require('../utils/make-fixes.js')

const sortImports = createEslintRule.createEslintRule({
name: 'sort-imports',
meta: {
type: 'suggestion',
docs: {
description: 'Enforce sorted imports.',
},
fixable: 'code',
schema: [
{
id: 'sort-imports',
type: 'object',
properties: {
type: {
description: 'Specifies the sorting method.',
type: 'string',
enum: ['alphabetical', 'natural', 'line-length'],
},
order: {
description:
'Determines whether the sorted items should be in ascending or descending order.',
type: 'string',
enum: ['asc', 'desc'],
},
matcher: {
description: 'Specifies the string matcher.',
type: 'string',
enum: ['minimatch', 'regex'],
},
ignoreCase: {
description:
'Controls whether sorting should be case-sensitive or not.',
type: 'boolean',
},
specialCharacters: {
description:
'Controls how special characters should be handled before sorting.',
type: 'string',
enum: ['remove', 'trim', 'keep'],
},
internalPattern: {
description: 'Specifies the pattern for internal modules.',
items: {
type: 'string',
},
type: 'array',
},
sortSideEffects: {
description:
'Controls whether side-effect imports should be sorted.',
type: 'boolean',
},
newlinesBetween: {
description:
'Specifies how new lines should be handled between import groups.',
enum: ['ignore', 'always', 'never'],
type: 'string',
},
maxLineLength: {
description: 'Specifies the maximum line length.',
type: 'integer',
minimum: 0,
exclusiveMinimum: true,
},
groups: {
description: 'Specifies the order of the groups.',
type: 'array',
items: {
oneOf: [
{
type: 'string',
},
{
type: 'array',
items: {
type: 'string',
},
},
],
},
},
customGroups: {
description: 'Specifies custom groups.',
type: 'object',
properties: {
type: {
type: 'object',
},
value: {
type: 'object',
},
},
additionalProperties: false,
},
environment: {
description: 'Specifies the environment.',
enum: ['node', 'bun'],
type: 'string',
},
},
allOf: [
{
$ref: '#/definitions/max-line-length-requires-line-length-type',
},
],
additionalProperties: false,
dependencies: {
maxLineLength: ['type'],
},
definitions: {
'is-line-length': {
properties: {
type: { enum: ['line-length'], type: 'string' },
},
required: ['type'],
type: 'object',
},
'max-line-length-requires-line-length-type': {
anyOf: [
{
not: {
required: ['maxLineLength'],
type: 'object',
},
type: 'object',
},
{
$ref: '#/definitions/is-line-length',
},
],
},
},
},
],
messages: {
unexpectedImportsGroupOrder:
'Expected "{{right}}" ({{rightGroup}}) to come before "{{left}}" ({{leftGroup}}).',
unexpectedImportsOrder: 'Expected "{{right}}" to come before "{{left}}".',
missedSpacingBetweenImports:
'Missed spacing between "{{left}}" and "{{right}}" imports.',
extraSpacingBetweenImports:
'Extra spacing between "{{left}}" and "{{right}}" imports.',
},
},
defaultOptions: [
{
type: 'alphabetical',
order: 'asc',
ignoreCase: true,
specialCharacters: 'keep',
internalPattern: ['~/**'],
sortSideEffects: false,
newlinesBetween: 'always',
maxLineLength: void 0,
matcher: 'minimatch',
groups: [
'type',
['builtin', 'external'],
'internal-type',
'internal',
['parent-type', 'sibling-type', 'index-type'],
['parent', 'sibling', 'index'],
'object',
'unknown',
],
customGroups: { type: {}, value: {} },
environment: 'node',
},
],
create: context => {

@@ -205,9 +46,8 @@ let settings = getSettings.getSettings(context.settings)

],
matcher: 'minimatch',
customGroups: { type: {}, value: {} },
internalPattern:
(userOptions == null ? void 0 : userOptions.matcher) === 'regex'
? ['^~/.*']
: ['~/**'],
customGroups: { value: {}, type: {} },
internalPattern: ['^~/.*'],
partitionByComment: false,
partitionByNewLine: false,
newlinesBetween: 'always',
specialCharacters: 'keep',
sortSideEffects: false,

@@ -217,3 +57,3 @@ type: 'alphabetical',

ignoreCase: true,
specialCharacters: 'keep',
locales: 'en-US',
order: 'asc',

@@ -249,2 +89,12 @@ }),

)
validateNewlinesAndPartitionConfiguration.validateNewlinesAndPartitionConfiguration(
options,
)
let tsConfigOutput = options.tsconfigRootDir
? readClosestTsConfigByPath.readClosestTsConfigByPath({
tsconfigRootDir: options.tsconfigRootDir,
filePath: context.physicalFilename,
contextCwd: context.cwd,
})
: null
let isSideEffectOnlyGroup = group => {

@@ -277,2 +127,6 @@ if (!group) {

let sourceCode = getSourceCode.getSourceCode(context)
let eslintDisabledLines = getEslintDisabledLines.getEslintDisabledLines({
ruleName: context.id,
sourceCode,
})
let nodes = []

@@ -283,12 +137,12 @@ let isSideEffectImport = node =>

0 /* Avoid matching on named imports without specifiers */ &&
!/}\s*from\s+/.test(sourceCode.getText(node))
let isStyle = value =>
['.less', '.scss', '.sass', '.styl', '.pcss', '.css', '.sss'].some(
extension => value.endsWith(extension),
!/\}\s*from\s+/u.test(sourceCode.getText(node))
let isStyle = value => {
let [cleanedValue] = value.split('?')
return ['.less', '.scss', '.sass', '.styl', '.pcss', '.css', '.sss'].some(
extension => cleanedValue.endsWith(extension),
)
let hasMultipleImportDeclarations = node => node.specifiers.length > 1
let flatGroups = options.groups.flat()
let shouldRegroupSideEffectNodes = flatGroups.includes('side-effect')
let shouldRegroupSideEffectStyleNodes =
flatGroups.includes('side-effect-style')
}
let flatGroups = new Set(options.groups.flat())
let shouldRegroupSideEffectNodes = flatGroups.has('side-effect')
let shouldRegroupSideEffectStyleNodes = flatGroups.has('side-effect-style')
let registerNode = node => {

@@ -300,5 +154,5 @@ let name

if (node.moduleReference.type === 'TSExternalModuleReference') {
name = `${node.moduleReference.expression.value}`
name = node.moduleReference.expression.value
} else {
name = sourceCode.text.slice(...node.moduleReference.range)
name = sourceCode.getText(node.moduleReference)
}

@@ -322,9 +176,7 @@ } else {

let isSibling = value => value.startsWith('./')
let { getGroup, defineGroup, setCustomGroups } =
let { setCustomGroups, defineGroup, getGroup } =
useGroups.useGroups(options)
let isInternal = value =>
let matchesInternalPattern = value =>
options.internalPattern.length &&
options.internalPattern.some(pattern =>
matches.matches(value, pattern, options.matcher),
)
options.internalPattern.some(pattern => matches.matches(value, pattern))
let isCoreModule = value => {

@@ -353,7 +205,42 @@ let bunModules = [

}
let isExternal = value =>
!(value.startsWith('.') || value.startsWith('/'))
let getInternalOrExternalGroup = value => {
var _a
let typescriptImport = getTypescriptImport.getTypescriptImport()
if (!typescriptImport) {
return !value.startsWith('.') && !value.startsWith('/')
? 'external'
: null
}
let isRelativeImport =
typescriptImport.isExternalModuleNameRelative(value)
if (isRelativeImport) {
return null
}
if (!tsConfigOutput) {
return 'external'
}
let resolution = typescriptImport.resolveModuleName(
value,
context.filename,
tsConfigOutput.compilerOptions,
typescriptImport.sys,
tsConfigOutput.cache,
)
if (
typeof ((_a = resolution.resolvedModule) == null
? void 0
: _a.isExternalLibraryImport) !== 'boolean'
) {
return 'external'
}
return resolution.resolvedModule.isExternalLibraryImport
? 'external'
: 'internal'
}
if (node.type !== 'VariableDeclaration' && node.importKind === 'type') {
if (node.type === 'ImportDeclaration') {
setCustomGroups(options.customGroups.type, node.source.value)
let internalExternalGroup = matchesInternalPattern(node.source.value)
? 'internal'
: getInternalOrExternalGroup(node.source.value)
if (isIndex(node.source.value)) {

@@ -368,3 +255,3 @@ defineGroup('index-type')

}
if (isInternal(node.source.value)) {
if (internalExternalGroup === 'internal') {
defineGroup('internal-type')

@@ -375,3 +262,3 @@ }

}
if (isExternal(node.source.value)) {
if (internalExternalGroup === 'external') {
defineGroup('external-type')

@@ -396,2 +283,5 @@ }

}
let internalExternalGroup = matchesInternalPattern(value)
? 'internal'
: getInternalOrExternalGroup(value)
let isStyleValue = isStyle(value)

@@ -418,3 +308,3 @@ isStyleSideEffect = isSideEffect && isStyleValue

}
if (isInternal(value)) {
if (internalExternalGroup === 'internal') {
defineGroup('internal')

@@ -425,3 +315,3 @@ }

}
if (isExternal(value)) {
if (internalExternalGroup === 'external') {
defineGroup('external')

@@ -431,5 +321,2 @@ }

nodes.push({
size: rangeToDiff.rangeToDiff(node.range),
group: getGroup(),
node,
isIgnored:

@@ -440,6 +327,16 @@ !options.sortSideEffects &&

(!isStyleSideEffect || !shouldRegroupSideEffectStyleNodes),
isEslintDisabled: isNodeEslintDisabled.isNodeEslintDisabled(
node,
eslintDisabledLines,
),
size: rangeToDiff.rangeToDiff(node, sourceCode),
addSafetySemicolonWhenInline: true,
group: getGroup(),
node,
name,
...(options.type === 'line-length' &&
options.maxLineLength && {
hasMultipleImportDeclarations: hasMultipleImportDeclarations(node),
hasMultipleImportDeclarations: isSortable.isSortable(
node.specifiers,
),
}),

@@ -449,41 +346,38 @@ })

return {
TSImportEqualsDeclaration: registerNode,
ImportDeclaration: registerNode,
VariableDeclaration: node => {
var _a
if (
node.declarations[0].init &&
node.declarations[0].init.type === 'CallExpression' &&
node.declarations[0].init.callee.type === 'Identifier' &&
node.declarations[0].init.callee.name === 'require' &&
((_a = node.declarations[0].init.arguments[0]) == null
? void 0
: _a.type) === 'Literal'
) {
registerNode(node)
}
},
'Program:exit': () => {
var _a
let hasContentBetweenNodes = (left, right) =>
getCommentsBefore.getCommentsBefore(right.node, sourceCode).length >
0 ||
!!sourceCode.getTokensBetween(left.node, right.node, {
includeComments: false,
}).length
let splitNodes = [[]]
for (let node of nodes) {
let lastNode = (_a = splitNodes.at(-1)) == null ? void 0 : _a.at(-1)
if (lastNode && hasContentBetweenNodes(lastNode, node)) {
splitNodes.push([node])
} else {
splitNodes.at(-1).push(node)
let formattedMembers = [[]]
for (let sortingNode of nodes) {
let lastSortingNode =
(_a = formattedMembers.at(-1)) == null ? void 0 : _a.at(-1)
if (
(options.partitionByComment &&
isPartitionComment.hasPartitionComment(
options.partitionByComment,
getCommentsBefore.getCommentsBefore(
sortingNode.node,
sourceCode,
),
)) ||
(options.partitionByNewLine &&
lastSortingNode &&
getLinesBetween.getLinesBetween(
sourceCode,
lastSortingNode,
sortingNode,
)) ||
(lastSortingNode &&
hasContentBetweenNodes(lastSortingNode, sortingNode))
) {
formattedMembers.push([])
}
formattedMembers.at(-1).push(sortingNode)
}
for (let nodeList of splitNodes) {
let sortedNodes = sortNodesByGroups.sortNodesByGroups(
nodeList,
options,
{
isNodeIgnored: node => node.isIgnored,
for (let nodeList of formattedMembers) {
let sortNodesExcludingEslintDisabled = ignoreEslintDisabledNodes =>
sortNodesByGroups.sortNodesByGroups(nodeList, options, {
getGroupCompareOptions: groupNumber => {

@@ -496,124 +390,68 @@ if (options.sortSideEffects) {

},
},
)
isNodeIgnored: node => node.isIgnored,
ignoreEslintDisabledNodes,
})
let sortedNodes = sortNodesExcludingEslintDisabled(false)
let sortedNodesExcludingEslintDisabled =
sortNodesExcludingEslintDisabled(true)
pairwise.pairwise(nodeList, (left, right) => {
let leftNum = getGroupNumber.getGroupNumber(options.groups, left)
let rightNum = getGroupNumber.getGroupNumber(options.groups, right)
let leftNumber = getGroupNumber.getGroupNumber(options.groups, left)
let rightNumber = getGroupNumber.getGroupNumber(
options.groups,
right,
)
let indexOfLeft = sortedNodes.indexOf(left)
let indexOfRight = sortedNodes.indexOf(right)
let indexOfRightExcludingEslintDisabled =
sortedNodesExcludingEslintDisabled.indexOf(right)
let messageIds = []
if (indexOfLeft > indexOfRight) {
if (
indexOfLeft > indexOfRight ||
indexOfLeft >= indexOfRightExcludingEslintDisabled
) {
messageIds.push(
leftNum !== rightNum
? 'unexpectedImportsGroupOrder'
: 'unexpectedImportsOrder',
leftNumber === rightNumber
? 'unexpectedImportsOrder'
: 'unexpectedImportsGroupOrder',
)
}
let numberOfEmptyLinesBetween = getLinesBetween.getLinesBetween(
sourceCode,
left,
right,
)
if (
options.newlinesBetween === 'never' &&
numberOfEmptyLinesBetween > 0
) {
messageIds.push('extraSpacingBetweenImports')
}
if (options.newlinesBetween === 'always') {
if (leftNum < rightNum && numberOfEmptyLinesBetween === 0) {
messageIds.push('missedSpacingBetweenImports')
} else if (
numberOfEmptyLinesBetween > 1 ||
(leftNum === rightNum && numberOfEmptyLinesBetween > 0)
) {
messageIds.push('extraSpacingBetweenImports')
}
}
messageIds = [
...messageIds,
...getNewlinesErrors.getNewlinesErrors({
missedSpacingError: 'missedSpacingBetweenImports',
extraSpacingError: 'extraSpacingBetweenImports',
rightNum: rightNumber,
leftNum: leftNumber,
sourceCode,
options,
right,
left,
}),
]
for (let messageId of messageIds) {
context.report({
messageId,
fix: fixer => [
...makeFixes.makeFixes({
sortedNodes: sortedNodesExcludingEslintDisabled,
nodes: nodeList,
sourceCode,
options,
fixer,
}),
...makeNewlinesFixes.makeNewlinesFixes({
sortedNodes: sortedNodesExcludingEslintDisabled,
nodes: nodeList,
sourceCode,
options,
fixer,
}),
],
data: {
left: left.name,
rightGroup: right.group,
leftGroup: left.group,
right: right.name,
rightGroup: right.group,
left: left.name,
},
node: right.node,
fix: fixer => {
let newlinesFixes = []
for (let max = sortedNodes.length, i = 0; i < max; i++) {
let node = sortedNodes.at(i)
let nextNode = sortedNodes.at(i + 1)
if (options.newlinesBetween === 'ignore' || !nextNode) {
continue
}
let nodeGroupNumber = getGroupNumber.getGroupNumber(
options.groups,
node,
)
let nextNodeGroupNumber = getGroupNumber.getGroupNumber(
options.groups,
nextNode,
)
let currentNodeRange = getNodeRange.getNodeRange(
nodeList.at(i).node,
sourceCode,
options,
)
let nextNodeRange =
getNodeRange
.getNodeRange(
nodeList.at(i + 1).node,
sourceCode,
options,
)
.at(0) - 1
let linesBetweenImports = getLinesBetween.getLinesBetween(
sourceCode,
nodeList.at(i),
nodeList.at(i + 1),
)
if (
(options.newlinesBetween === 'always' &&
nodeGroupNumber === nextNodeGroupNumber &&
linesBetweenImports !== 0) ||
(options.newlinesBetween === 'never' &&
linesBetweenImports > 0)
) {
newlinesFixes.push(
fixer.removeRange([
currentNodeRange.at(1),
nextNodeRange,
]),
)
}
if (
options.newlinesBetween === 'always' &&
nodeGroupNumber !== nextNodeGroupNumber
) {
if (linesBetweenImports > 1) {
newlinesFixes.push(
fixer.replaceTextRange(
[currentNodeRange.at(1), nextNodeRange],
'\n',
),
)
} else if (linesBetweenImports === 0) {
newlinesFixes.push(
fixer.insertTextAfterRange(currentNodeRange, '\n'),
)
}
}
}
return [
...makeFixes.makeFixes(
fixer,
nodeList,
sortedNodes,
sourceCode,
),
...newlinesFixes,
]
},
messageId,
})

@@ -624,5 +462,161 @@ }

},
VariableDeclaration: node => {
var _a
if (
node.declarations[0].init &&
node.declarations[0].init.type === 'CallExpression' &&
node.declarations[0].init.callee.type === 'Identifier' &&
node.declarations[0].init.callee.name === 'require' &&
((_a = node.declarations[0].init.arguments[0]) == null
? void 0
: _a.type) === 'Literal'
) {
registerNode(node)
}
},
TSImportEqualsDeclaration: registerNode,
ImportDeclaration: registerNode,
}
},
meta: {
schema: [
{
properties: {
customGroups: {
properties: {
value: {
description: 'Specifies custom groups for value imports.',
type: 'object',
},
type: {
description: 'Specifies custom groups for type imports.',
type: 'object',
},
},
description: 'Specifies custom groups.',
additionalProperties: false,
type: 'object',
},
partitionByComment: {
...commonJsonSchemas.partitionByCommentJsonSchema,
description:
'Allows you to use comments to separate the interface properties into logical groups.',
},
internalPattern: {
description: 'Specifies the pattern for internal modules.',
items: {
type: 'string',
},
type: 'array',
},
maxLineLength: {
description: 'Specifies the maximum line length.',
exclusiveMinimum: true,
type: 'integer',
minimum: 0,
},
sortSideEffects: {
description:
'Controls whether side-effect imports should be sorted.',
type: 'boolean',
},
environment: {
description: 'Specifies the environment.',
enum: ['node', 'bun'],
type: 'string',
},
tsconfigRootDir: {
description: 'Specifies the tsConfig root directory.',
type: 'string',
},
partitionByNewLine: commonJsonSchemas.partitionByNewLineJsonSchema,
specialCharacters: commonJsonSchemas.specialCharactersJsonSchema,
newlinesBetween: commonJsonSchemas.newlinesBetweenJsonSchema,
ignoreCase: commonJsonSchemas.ignoreCaseJsonSchema,
locales: commonJsonSchemas.localesJsonSchema,
groups: commonJsonSchemas.groupsJsonSchema,
order: commonJsonSchemas.orderJsonSchema,
type: commonJsonSchemas.typeJsonSchema,
},
definitions: {
'max-line-length-requires-line-length-type': {
anyOf: [
{
not: {
required: ['maxLineLength'],
type: 'object',
},
type: 'object',
},
{
$ref: '#/definitions/is-line-length',
},
],
},
'is-line-length': {
properties: {
type: { enum: ['line-length'], type: 'string' },
},
required: ['type'],
type: 'object',
},
},
allOf: [
{
$ref: '#/definitions/max-line-length-requires-line-length-type',
},
],
dependencies: {
maxLineLength: ['type'],
},
additionalProperties: false,
id: 'sort-imports',
type: 'object',
},
],
messages: {
unexpectedImportsGroupOrder:
'Expected "{{right}}" ({{rightGroup}}) to come before "{{left}}" ({{leftGroup}}).',
missedSpacingBetweenImports:
'Missed spacing between "{{left}}" and "{{right}}" imports.',
extraSpacingBetweenImports:
'Extra spacing between "{{left}}" and "{{right}}" imports.',
unexpectedImportsOrder: 'Expected "{{right}}" to come before "{{left}}".',
},
docs: {
url: 'https://perfectionist.dev/rules/sort-imports',
description: 'Enforce sorted imports.',
recommended: true,
},
type: 'suggestion',
fixable: 'code',
},
defaultOptions: [
{
groups: [
'type',
['builtin', 'external'],
'internal-type',
'internal',
['parent-type', 'sibling-type', 'index-type'],
['parent', 'sibling', 'index'],
'object',
'unknown',
],
customGroups: { value: {}, type: {} },
partitionByComment: false,
partitionByNewLine: false,
specialCharacters: 'keep',
internalPattern: ['~/**'],
newlinesBetween: 'always',
sortSideEffects: false,
type: 'alphabetical',
environment: 'node',
ignoreCase: true,
locales: 'en-US',
order: 'asc',
},
],
name: 'sort-imports',
})
module.exports = sortImports
'use strict'
const commonJsonSchemas = require('../utils/common-json-schemas.js')
const validateNewlinesAndPartitionConfiguration = require('../utils/validate-newlines-and-partition-configuration.js')
const validateGroupsConfiguration = require('../utils/validate-groups-configuration.js')
const getEslintDisabledLines = require('../utils/get-eslint-disabled-lines.js')
const isNodeEslintDisabled = require('../utils/is-node-eslint-disabled.js')
const isPartitionComment = require('../utils/is-partition-comment.js')
const sortNodesByGroups = require('../utils/sort-nodes-by-groups.js')
const getCommentsBefore = require('../utils/get-comments-before.js')
const makeNewlinesFixes = require('../utils/make-newlines-fixes.js')
const getNewlinesErrors = require('../utils/get-newlines-errors.js')
const createEslintRule = require('../utils/create-eslint-rule.js')

@@ -11,5 +17,5 @@ const isMemberOptional = require('../utils/is-member-optional.js')

const getSourceCode = require('../utils/get-source-code.js')
const toSingleLine = require('../utils/to-single-line.js')
const rangeToDiff = require('../utils/range-to-diff.js')
const getSettings = require('../utils/get-settings.js')
const isSortable = require('../utils/is-sortable.js')
const useGroups = require('../utils/use-groups.js')

@@ -20,41 +26,224 @@ const makeFixes = require('../utils/make-fixes.js')

const matches = require('../utils/matches.js')
let defaultOptions = {
partitionByComment: false,
partitionByNewLine: false,
newlinesBetween: 'ignore',
specialCharacters: 'keep',
type: 'alphabetical',
groupKind: 'mixed',
ignorePattern: [],
ignoreCase: true,
customGroups: {},
locales: 'en-US',
order: 'asc',
groups: [],
}
const sortInterfaces = createEslintRule.createEslintRule({
name: 'sort-interfaces',
create: context => ({
TSInterfaceDeclaration: node => {
if (!isSortable.isSortable(node.body.body)) {
return
}
let settings = getSettings.getSettings(context.settings)
let options = complete.complete(
context.options.at(0),
settings,
defaultOptions,
)
validateGroupsConfiguration.validateGroupsConfiguration(
options.groups,
['multiline', 'method', 'unknown'],
Object.keys(options.customGroups),
)
validateNewlinesAndPartitionConfiguration.validateNewlinesAndPartitionConfiguration(
options,
)
if (
options.ignorePattern.some(pattern =>
matches.matches(node.id.name, pattern),
)
) {
return
}
let sourceCode = getSourceCode.getSourceCode(context)
let eslintDisabledLines = getEslintDisabledLines.getEslintDisabledLines({
ruleName: context.id,
sourceCode,
})
let formattedMembers = node.body.body.reduce(
(accumulator, element) => {
var _a, _b, _c, _d, _e
if (element.type === 'TSCallSignatureDeclaration') {
accumulator.push([])
return accumulator
}
let lastElement =
(_a = accumulator.at(-1)) == null ? void 0 : _a.at(-1)
let name
let { setCustomGroups, defineGroup, getGroup } =
useGroups.useGroups(options)
if (element.type === 'TSPropertySignature') {
if (element.key.type === 'Identifier') {
;({ name } = element.key)
} else if (element.key.type === 'Literal') {
name = `${element.key.value}`
} else {
let end =
((_b = element.typeAnnotation) == null
? void 0
: _b.range.at(0)) ??
element.range.at(1) - (element.optional ? '?'.length : 0)
name = sourceCode.text.slice(element.range.at(0), end)
}
} else if (element.type === 'TSIndexSignature') {
let endIndex =
((_c = element.typeAnnotation) == null
? void 0
: _c.range.at(0)) ?? element.range.at(1)
name = sourceCode.text.slice(element.range.at(0), endIndex)
} else {
let endIndex =
((_d = element.returnType) == null ? void 0 : _d.range.at(0)) ??
element.range.at(1)
name = sourceCode.text.slice(element.range.at(0), endIndex)
}
setCustomGroups(options.customGroups, name)
if (
element.type === 'TSMethodSignature' ||
(element.type === 'TSPropertySignature' &&
((_e = element.typeAnnotation) == null
? void 0
: _e.typeAnnotation.type) === 'TSFunctionType')
) {
defineGroup('method')
}
if (element.loc.start.line !== element.loc.end.line) {
defineGroup('multiline')
}
let elementSortingNode = {
isEslintDisabled: isNodeEslintDisabled.isNodeEslintDisabled(
element,
eslintDisabledLines,
),
groupKind: isMemberOptional.isMemberOptional(element)
? 'optional'
: 'required',
size: rangeToDiff.rangeToDiff(element, sourceCode),
addSafetySemicolonWhenInline: true,
group: getGroup(),
node: element,
name,
}
if (
(options.partitionByComment &&
isPartitionComment.hasPartitionComment(
options.partitionByComment,
getCommentsBefore.getCommentsBefore(element, sourceCode),
)) ||
(options.partitionByNewLine &&
lastElement &&
getLinesBetween.getLinesBetween(
sourceCode,
lastElement,
elementSortingNode,
))
) {
accumulator.push([])
}
accumulator.at(-1).push(elementSortingNode)
return accumulator
},
[[]],
)
let groupKindOrder
if (options.groupKind === 'required-first') {
groupKindOrder = ['required', 'optional']
} else if (options.groupKind === 'optional-first') {
groupKindOrder = ['optional', 'required']
} else {
groupKindOrder = ['any']
}
for (let nodes of formattedMembers) {
let filteredGroupKindNodes = groupKindOrder.map(groupKind =>
nodes.filter(
currentNode =>
groupKind === 'any' || currentNode.groupKind === groupKind,
),
)
let sortNodesExcludingEslintDisabled = ignoreEslintDisabledNodes =>
filteredGroupKindNodes.flatMap(groupedNodes =>
sortNodesByGroups.sortNodesByGroups(groupedNodes, options, {
ignoreEslintDisabledNodes,
}),
)
let sortedNodes = sortNodesExcludingEslintDisabled(false)
let sortedNodesExcludingEslintDisabled =
sortNodesExcludingEslintDisabled(true)
pairwise.pairwise(nodes, (left, right) => {
let leftNumber = getGroupNumber.getGroupNumber(options.groups, left)
let rightNumber = getGroupNumber.getGroupNumber(options.groups, right)
let indexOfLeft = sortedNodes.indexOf(left)
let indexOfRight = sortedNodes.indexOf(right)
let indexOfRightExcludingEslintDisabled =
sortedNodesExcludingEslintDisabled.indexOf(right)
let messageIds = []
if (
indexOfLeft > indexOfRight ||
indexOfLeft >= indexOfRightExcludingEslintDisabled
) {
messageIds.push(
leftNumber === rightNumber
? 'unexpectedInterfacePropertiesOrder'
: 'unexpectedInterfacePropertiesGroupOrder',
)
}
messageIds = [
...messageIds,
...getNewlinesErrors.getNewlinesErrors({
missedSpacingError: 'missedSpacingBetweenInterfaceMembers',
extraSpacingError: 'extraSpacingBetweenInterfaceMembers',
rightNum: rightNumber,
leftNum: leftNumber,
sourceCode,
options,
right,
left,
}),
]
for (let messageId of messageIds) {
context.report({
fix: fixer => [
...makeFixes.makeFixes({
sortedNodes: sortedNodesExcludingEslintDisabled,
sourceCode,
options,
fixer,
nodes,
}),
...makeNewlinesFixes.makeNewlinesFixes({
sortedNodes: sortedNodesExcludingEslintDisabled,
sourceCode,
options,
fixer,
nodes,
}),
],
data: {
rightGroup: right.group,
leftGroup: left.group,
right: right.name,
left: left.name,
},
node: right.node,
messageId,
})
}
})
}
},
}),
meta: {
type: 'suggestion',
docs: {
description: 'Enforce sorted interface properties.',
},
fixable: 'code',
schema: [
{
type: 'object',
properties: {
type: {
description: 'Specifies the sorting method.',
type: 'string',
enum: ['alphabetical', 'natural', 'line-length'],
},
order: {
description:
'Determines whether the sorted items should be in ascending or descending order.',
type: 'string',
enum: ['asc', 'desc'],
},
matcher: {
description: 'Specifies the string matcher.',
type: 'string',
enum: ['minimatch', 'regex'],
},
ignoreCase: {
description:
'Controls whether sorting should be case-sensitive or not.',
type: 'boolean',
},
specialCharacters: {
description:
'Controls how special characters should be handled before sorting.',
type: 'string',
enum: ['remove', 'trim', 'keep'],
},
ignorePattern: {

@@ -69,24 +258,6 @@ description:

partitionByComment: {
...commonJsonSchemas.partitionByCommentJsonSchema,
description:
'Allows you to use comments to separate the interface properties into logical groups.',
anyOf: [
{
type: 'boolean',
},
{
type: 'string',
},
{
type: 'array',
items: {
type: 'string',
},
},
],
},
partitionByNewLine: {
description:
'Allows to use spaces to separate the nodes into logical groups.',
type: 'boolean',
},
groupKind: {

@@ -97,38 +268,14 @@ description: 'Specifies the order of optional and required nodes.',

},
groups: {
description: 'Specifies the order of the groups.',
type: 'array',
items: {
oneOf: [
{
type: 'string',
},
{
type: 'array',
items: {
type: 'string',
},
},
],
},
},
customGroups: {
description: 'Specifies custom groups.',
type: 'object',
additionalProperties: {
oneOf: [
{
type: 'string',
},
{
type: 'array',
items: {
type: 'string',
},
},
],
},
},
partitionByNewLine: commonJsonSchemas.partitionByNewLineJsonSchema,
specialCharacters: commonJsonSchemas.specialCharactersJsonSchema,
newlinesBetween: commonJsonSchemas.newlinesBetweenJsonSchema,
customGroups: commonJsonSchemas.customGroupsJsonSchema,
ignoreCase: commonJsonSchemas.ignoreCaseJsonSchema,
locales: commonJsonSchemas.localesJsonSchema,
groups: commonJsonSchemas.groupsJsonSchema,
order: commonJsonSchemas.orderJsonSchema,
type: commonJsonSchemas.typeJsonSchema,
},
additionalProperties: false,
type: 'object',
},

@@ -139,205 +286,20 @@ ],

'Expected "{{right}}" ({{rightGroup}}) to come before "{{left}}" ({{leftGroup}}).',
missedSpacingBetweenInterfaceMembers:
'Missed spacing between "{{left}}" and "{{right}}" interfaces.',
extraSpacingBetweenInterfaceMembers:
'Extra spacing between "{{left}}" and "{{right}}" interfaces.',
unexpectedInterfacePropertiesOrder:
'Expected "{{right}}" to come before "{{left}}".',
},
docs: {
url: 'https://perfectionist.dev/rules/sort-interfaces',
description: 'Enforce sorted interface properties.',
recommended: true,
},
type: 'suggestion',
fixable: 'code',
},
defaultOptions: [
{
type: 'alphabetical',
order: 'asc',
ignoreCase: true,
specialCharacters: 'keep',
matcher: 'minimatch',
ignorePattern: [],
partitionByComment: false,
partitionByNewLine: false,
groupKind: 'mixed',
groups: [],
customGroups: {},
},
],
create: context => ({
TSInterfaceDeclaration: node => {
if (node.body.body.length > 1) {
let settings = getSettings.getSettings(context.settings)
let options = complete.complete(context.options.at(0), settings, {
partitionByComment: false,
partitionByNewLine: false,
type: 'alphabetical',
groupKind: 'mixed',
matcher: 'minimatch',
ignorePattern: [],
ignoreCase: true,
specialCharacters: 'keep',
customGroups: {},
order: 'asc',
groups: [],
})
validateGroupsConfiguration.validateGroupsConfiguration(
options.groups,
['multiline', 'method', 'unknown'],
Object.keys(options.customGroups),
)
let sourceCode = getSourceCode.getSourceCode(context)
let partitionComment = options.partitionByComment
if (
!options.ignorePattern.some(pattern =>
matches.matches(node.id.name, pattern, options.matcher),
)
) {
let formattedMembers = node.body.body.reduce(
(accumulator, element) => {
var _a, _b, _c, _d, _e
if (element.type === 'TSCallSignatureDeclaration') {
accumulator.push([])
return accumulator
}
let lastElement =
(_a = accumulator.at(-1)) == null ? void 0 : _a.at(-1)
let name
let { getGroup, defineGroup, setCustomGroups } =
useGroups.useGroups(options)
if (element.type === 'TSPropertySignature') {
if (element.key.type === 'Identifier') {
;({ name } = element.key)
} else if (element.key.type === 'Literal') {
name = `${element.key.value}`
} else {
let end =
((_b = element.typeAnnotation) == null
? void 0
: _b.range.at(0)) ??
element.range.at(1) - (element.optional ? '?'.length : 0)
name = sourceCode.text.slice(element.range.at(0), end)
}
} else if (element.type === 'TSIndexSignature') {
let endIndex =
((_c = element.typeAnnotation) == null
? void 0
: _c.range.at(0)) ?? element.range.at(1)
name = sourceCode.text.slice(element.range.at(0), endIndex)
} else {
let endIndex =
((_d = element.returnType) == null
? void 0
: _d.range.at(0)) ?? element.range.at(1)
name = sourceCode.text.slice(element.range.at(0), endIndex)
}
setCustomGroups(options.customGroups, name)
if (
element.type === 'TSMethodSignature' ||
(element.type === 'TSPropertySignature' &&
((_e = element.typeAnnotation) == null
? void 0
: _e.typeAnnotation.type) === 'TSFunctionType')
) {
defineGroup('method')
}
if (element.loc.start.line !== element.loc.end.line) {
defineGroup('multiline')
}
let elementSortingNode = {
size: rangeToDiff.rangeToDiff(element.range),
node: element,
group: getGroup(),
name,
}
if (
(partitionComment &&
isPartitionComment.hasPartitionComment(
partitionComment,
getCommentsBefore.getCommentsBefore(element, sourceCode),
options.matcher,
)) ||
(options.partitionByNewLine &&
lastElement &&
getLinesBetween.getLinesBetween(
sourceCode,
lastElement,
elementSortingNode,
))
) {
accumulator.push([])
}
accumulator.at(-1).push(elementSortingNode)
return accumulator
},
[[]],
)
let { groupKind } = options
for (let nodes of formattedMembers) {
let sortedNodes
if (groupKind !== 'mixed') {
let optionalNodes = nodes.filter(member =>
isMemberOptional.isMemberOptional(member.node),
)
let requiredNodes = nodes.filter(
member => !isMemberOptional.isMemberOptional(member.node),
)
sortedNodes =
groupKind === 'optional-first'
? [
...sortNodesByGroups.sortNodesByGroups(
optionalNodes,
options,
),
...sortNodesByGroups.sortNodesByGroups(
requiredNodes,
options,
),
]
: [
...sortNodesByGroups.sortNodesByGroups(
requiredNodes,
options,
),
...sortNodesByGroups.sortNodesByGroups(
optionalNodes,
options,
),
]
} else {
sortedNodes = sortNodesByGroups.sortNodesByGroups(nodes, options)
}
pairwise.pairwise(nodes, (left, right) => {
let indexOfLeft = sortedNodes.indexOf(left)
let indexOfRight = sortedNodes.indexOf(right)
if (indexOfLeft > indexOfRight) {
let leftNum = getGroupNumber.getGroupNumber(
options.groups,
left,
)
let rightNum = getGroupNumber.getGroupNumber(
options.groups,
right,
)
context.report({
messageId:
leftNum !== rightNum
? 'unexpectedInterfacePropertiesGroupOrder'
: 'unexpectedInterfacePropertiesOrder',
data: {
left: toSingleLine.toSingleLine(left.name),
leftGroup: left.group,
right: toSingleLine.toSingleLine(right.name),
rightGroup: right.group,
},
node: right.node,
fix: fixer =>
makeFixes.makeFixes(
fixer,
nodes,
sortedNodes,
sourceCode,
options,
),
})
}
})
}
}
}
},
}),
defaultOptions: [defaultOptions],
name: 'sort-interfaces',
})
module.exports = sortInterfaces
'use strict'
const commonJsonSchemas = require('../utils/common-json-schemas.js')
const validateNewlinesAndPartitionConfiguration = require('../utils/validate-newlines-and-partition-configuration.js')
const validateGroupsConfiguration = require('../utils/validate-groups-configuration.js')
const getEslintDisabledLines = require('../utils/get-eslint-disabled-lines.js')
const isNodeEslintDisabled = require('../utils/is-node-eslint-disabled.js')
const isPartitionComment = require('../utils/is-partition-comment.js')
const sortNodesByGroups = require('../utils/sort-nodes-by-groups.js')
const getCommentsBefore = require('../utils/get-comments-before.js')
const makeNewlinesFixes = require('../utils/make-newlines-fixes.js')
const getNewlinesErrors = require('../utils/get-newlines-errors.js')
const createEslintRule = require('../utils/create-eslint-rule.js')

@@ -17,117 +23,22 @@ const getLinesBetween = require('../utils/get-lines-between.js')

const pairwise = require('../utils/pairwise.js')
let defaultOptions = {
specialCharacters: 'keep',
newlinesBetween: 'ignore',
partitionByComment: false,
partitionByNewLine: false,
type: 'alphabetical',
ignoreCase: true,
locales: 'en-US',
order: 'asc',
groups: [],
}
const sortIntersectionTypes = createEslintRule.createEslintRule({
name: 'sort-intersection-types',
meta: {
type: 'suggestion',
docs: {
description: 'Enforce sorted intersection types.',
},
fixable: 'code',
schema: [
{
type: 'object',
properties: {
type: {
description: 'Specifies the sorting method.',
type: 'string',
enum: ['alphabetical', 'natural', 'line-length'],
},
order: {
description:
'Determines whether the sorted items should be in ascending or descending order.',
type: 'string',
enum: ['asc', 'desc'],
},
matcher: {
description: 'Specifies the string matcher.',
type: 'string',
enum: ['minimatch', 'regex'],
},
ignoreCase: {
description:
'Controls whether sorting should be case-sensitive or not.',
type: 'boolean',
},
specialCharacters: {
description:
'Controls how special characters should be handled before sorting.',
type: 'string',
enum: ['remove', 'trim', 'keep'],
},
groups: {
description: 'Specifies the order of the groups.',
type: 'array',
items: {
oneOf: [
{
type: 'string',
},
{
type: 'array',
items: {
type: 'string',
},
},
],
},
},
partitionByComment: {
description:
'Allows you to use comments to separate the intersection types members into logical groups.',
anyOf: [
{
type: 'array',
items: {
type: 'string',
},
},
{
type: 'boolean',
},
{
type: 'string',
},
],
},
partitionByNewLine: {
description:
'Allows to use spaces to separate the nodes into logical groups.',
type: 'boolean',
},
},
additionalProperties: false,
},
],
messages: {
unexpectedIntersectionTypesGroupOrder:
'Expected "{{right}}" ({{rightGroup}}) to come before "{{left}}" ({{leftGroup}}).',
unexpectedIntersectionTypesOrder:
'Expected "{{right}}" to come before "{{left}}".',
},
},
defaultOptions: [
{
type: 'alphabetical',
order: 'asc',
ignoreCase: true,
specialCharacters: 'keep',
matcher: 'minimatch',
partitionByNewLine: false,
partitionByComment: false,
groups: [],
},
],
create: context => ({
TSIntersectionType: node => {
let settings = getSettings.getSettings(context.settings)
let options = complete.complete(context.options.at(0), settings, {
type: 'alphabetical',
ignoreCase: true,
specialCharacters: 'keep',
order: 'asc',
matcher: 'minimatch',
partitionByComment: false,
partitionByNewLine: false,
groups: [],
})
let options = complete.complete(
context.options.at(0),
settings,
defaultOptions,
)
validateGroupsConfiguration.validateGroupsConfiguration(

@@ -152,9 +63,34 @@ options.groups,

)
validateNewlinesAndPartitionConfiguration.validateNewlinesAndPartitionConfiguration(
options,
)
let sourceCode = getSourceCode.getSourceCode(context)
let partitionComment = options.partitionByComment
let eslintDisabledLines = getEslintDisabledLines.getEslintDisabledLines({
ruleName: context.id,
sourceCode,
})
let formattedMembers = node.types.reduce(
(accumulator, type) => {
var _a, _b
let { getGroup, defineGroup } = useGroups.useGroups(options)
let { defineGroup, getGroup } = useGroups.useGroups(options)
switch (type.type) {
case 'TSTemplateLiteralType':
case 'TSLiteralType':
defineGroup('literal')
break
case 'TSIndexedAccessType':
case 'TSTypeReference':
case 'TSQualifiedName':
case 'TSArrayType':
case 'TSInferType':
defineGroup('named')
break
case 'TSIntersectionType':
defineGroup('intersection')
break
case 'TSUndefinedKeyword':
case 'TSNullKeyword':
case 'TSVoidKeyword':
defineGroup('nullish')
break
case 'TSConditionalType':

@@ -167,12 +103,5 @@ defineGroup('conditional')

break
case 'TSImportType':
defineGroup('import')
break
case 'TSIntersectionType':
defineGroup('intersection')
break
case 'TSAnyKeyword':
case 'TSBooleanKeyword':
case 'TSUnknownKeyword':
case 'TSBigIntKeyword':
case 'TSBooleanKeyword':
case 'TSNeverKeyword':
case 'TSNumberKeyword':

@@ -182,25 +111,17 @@ case 'TSObjectKeyword':

case 'TSSymbolKeyword':
case 'TSNeverKeyword':
case 'TSAnyKeyword':
case 'TSThisType':
case 'TSUnknownKeyword':
case 'TSIntrinsicKeyword':
defineGroup('keyword')
break
case 'TSLiteralType':
case 'TSTemplateLiteralType':
defineGroup('literal')
case 'TSTypeOperator':
case 'TSTypeQuery':
defineGroup('operator')
break
case 'TSArrayType':
case 'TSIndexedAccessType':
case 'TSInferType':
case 'TSTypeReference':
case 'TSQualifiedName':
defineGroup('named')
break
case 'TSTypeLiteral':
case 'TSMappedType':
case 'TSTypeLiteral':
defineGroup('object')
break
case 'TSTypeQuery':
case 'TSTypeOperator':
defineGroup('operator')
case 'TSImportType':
defineGroup('import')
break

@@ -213,7 +134,2 @@ case 'TSTupleType':

break
case 'TSNullKeyword':
case 'TSUndefinedKeyword':
case 'TSVoidKeyword':
defineGroup('nullish')
break
}

@@ -223,4 +139,8 @@ let lastSortingNode =

let sortingNode = {
name: sourceCode.text.slice(...type.range),
size: rangeToDiff.rangeToDiff(type.range),
isEslintDisabled: isNodeEslintDisabled.isNodeEslintDisabled(
type,
eslintDisabledLines,
),
size: rangeToDiff.rangeToDiff(type, sourceCode),
name: sourceCode.getText(type),
group: getGroup(),

@@ -230,7 +150,6 @@ node: type,

if (
(partitionComment &&
(options.partitionByComment &&
isPartitionComment.hasPartitionComment(
partitionComment,
getCommentsBefore.getCommentsBefore(type, sourceCode),
options.matcher,
options.partitionByComment,
getCommentsBefore.getCommentsBefore(type, sourceCode, '&'),
)) ||

@@ -253,29 +172,66 @@ (options.partitionByNewLine &&

for (let nodes of formattedMembers) {
let sortedNodes = sortNodesByGroups.sortNodesByGroups(nodes, options)
let sortNodesExcludingEslintDisabled = ignoreEslintDisabledNodes =>
sortNodesByGroups.sortNodesByGroups(nodes, options, {
ignoreEslintDisabledNodes,
})
let sortedNodes = sortNodesExcludingEslintDisabled(false)
let sortedNodesExcludingEslintDisabled =
sortNodesExcludingEslintDisabled(true)
pairwise.pairwise(nodes, (left, right) => {
let leftNumber = getGroupNumber.getGroupNumber(options.groups, left)
let rightNumber = getGroupNumber.getGroupNumber(options.groups, right)
let indexOfLeft = sortedNodes.indexOf(left)
let indexOfRight = sortedNodes.indexOf(right)
if (indexOfLeft > indexOfRight) {
let leftNum = getGroupNumber.getGroupNumber(options.groups, left)
let rightNum = getGroupNumber.getGroupNumber(options.groups, right)
let indexOfRightExcludingEslintDisabled =
sortedNodesExcludingEslintDisabled.indexOf(right)
let messageIds = []
if (
indexOfLeft > indexOfRight ||
indexOfLeft >= indexOfRightExcludingEslintDisabled
) {
messageIds.push(
leftNumber === rightNumber
? 'unexpectedIntersectionTypesOrder'
: 'unexpectedIntersectionTypesGroupOrder',
)
}
messageIds = [
...messageIds,
...getNewlinesErrors.getNewlinesErrors({
missedSpacingError: 'missedSpacingBetweenIntersectionTypes',
extraSpacingError: 'extraSpacingBetweenIntersectionTypes',
rightNum: rightNumber,
leftNum: leftNumber,
sourceCode,
options,
right,
left,
}),
]
for (let messageId of messageIds) {
context.report({
messageId:
leftNum !== rightNum
? 'unexpectedIntersectionTypesGroupOrder'
: 'unexpectedIntersectionTypesOrder',
fix: fixer => [
...makeFixes.makeFixes({
sortedNodes: sortedNodesExcludingEslintDisabled,
sourceCode,
options,
fixer,
nodes,
}),
...makeNewlinesFixes.makeNewlinesFixes({
sortedNodes: sortedNodesExcludingEslintDisabled,
sourceCode,
options,
fixer,
nodes,
}),
],
data: {
right: toSingleLine.toSingleLine(right.name),
left: toSingleLine.toSingleLine(left.name),
rightGroup: right.group,
leftGroup: left.group,
right: toSingleLine.toSingleLine(right.name),
rightGroup: right.group,
},
node: right.node,
fix: fixer =>
makeFixes.makeFixes(
fixer,
nodes,
sortedNodes,
sourceCode,
options,
),
messageId,
})

@@ -287,3 +243,45 @@ }

}),
meta: {
schema: [
{
properties: {
partitionByComment: {
...commonJsonSchemas.partitionByCommentJsonSchema,
description:
'Allows you to use comments to separate the intersection types members into logical groups.',
},
partitionByNewLine: commonJsonSchemas.partitionByNewLineJsonSchema,
specialCharacters: commonJsonSchemas.specialCharactersJsonSchema,
newlinesBetween: commonJsonSchemas.newlinesBetweenJsonSchema,
ignoreCase: commonJsonSchemas.ignoreCaseJsonSchema,
locales: commonJsonSchemas.localesJsonSchema,
groups: commonJsonSchemas.groupsJsonSchema,
order: commonJsonSchemas.orderJsonSchema,
type: commonJsonSchemas.typeJsonSchema,
},
additionalProperties: false,
type: 'object',
},
],
messages: {
unexpectedIntersectionTypesGroupOrder:
'Expected "{{right}}" ({{rightGroup}}) to come before "{{left}}" ({{leftGroup}}).',
missedSpacingBetweenIntersectionTypes:
'Missed spacing between "{{left}}" and "{{right}}" types.',
extraSpacingBetweenIntersectionTypes:
'Extra spacing between "{{left}}" and "{{right}}" types.',
unexpectedIntersectionTypesOrder:
'Expected "{{right}}" to come before "{{left}}".',
},
docs: {
url: 'https://perfectionist.dev/rules/sort-intersection-types',
description: 'Enforce sorted intersection types.',
recommended: true,
},
type: 'suggestion',
fixable: 'code',
},
defaultOptions: [defaultOptions],
name: 'sort-intersection-types',
})
module.exports = sortIntersectionTypes
'use strict'
const commonJsonSchemas = require('../utils/common-json-schemas.js')
const validateGroupsConfiguration = require('../utils/validate-groups-configuration.js')
const getEslintDisabledLines = require('../utils/get-eslint-disabled-lines.js')
const isNodeEslintDisabled = require('../utils/is-node-eslint-disabled.js')
const sortNodesByGroups = require('../utils/sort-nodes-by-groups.js')

@@ -9,2 +12,3 @@ const createEslintRule = require('../utils/create-eslint-rule.js')

const getSettings = require('../utils/get-settings.js')
const isSortable = require('../utils/is-sortable.js')
const useGroups = require('../utils/use-groups.js')

@@ -15,41 +19,130 @@ const makeFixes = require('../utils/make-fixes.js')

const matches = require('../utils/matches.js')
let defaultOptions = {
specialCharacters: 'keep',
type: 'alphabetical',
ignorePattern: [],
ignoreCase: true,
customGroups: {},
locales: 'en-US',
order: 'asc',
groups: [],
}
const sortJsxProps = createEslintRule.createEslintRule({
name: 'sort-jsx-props',
create: context => ({
JSXElement: node => {
if (!isSortable.isSortable(node.openingElement.attributes)) {
return
}
let settings = getSettings.getSettings(context.settings)
let options = complete.complete(
context.options.at(0),
settings,
defaultOptions,
)
validateGroupsConfiguration.validateGroupsConfiguration(
options.groups,
['multiline', 'shorthand', 'unknown'],
Object.keys(options.customGroups),
)
let sourceCode = getSourceCode.getSourceCode(context)
let shouldIgnore = false
if (options.ignorePattern.length) {
let tagName = sourceCode.getText(node.openingElement.name)
shouldIgnore = options.ignorePattern.some(pattern =>
matches.matches(tagName, pattern),
)
}
if (
shouldIgnore ||
!isSortable.isSortable(node.openingElement.attributes)
) {
return
}
let eslintDisabledLines = getEslintDisabledLines.getEslintDisabledLines({
ruleName: context.id,
sourceCode,
})
let parts = node.openingElement.attributes.reduce(
(accumulator, attribute) => {
if (attribute.type === 'JSXSpreadAttribute') {
accumulator.push([])
return accumulator
}
let name =
attribute.name.type === 'JSXNamespacedName'
? `${attribute.name.namespace.name}:${attribute.name.name.name}`
: attribute.name.name
let { setCustomGroups, defineGroup, getGroup } =
useGroups.useGroups(options)
setCustomGroups(options.customGroups, name)
if (attribute.value === null) {
defineGroup('shorthand')
}
if (attribute.loc.start.line !== attribute.loc.end.line) {
defineGroup('multiline')
}
let jsxNode = {
isEslintDisabled: isNodeEslintDisabled.isNodeEslintDisabled(
attribute,
eslintDisabledLines,
),
size: rangeToDiff.rangeToDiff(attribute, sourceCode),
group: getGroup(),
node: attribute,
name,
}
accumulator.at(-1).push(jsxNode)
return accumulator
},
[[]],
)
for (let nodes of parts) {
let sortNodesExcludingEslintDisabled = ignoreEslintDisabledNodes =>
sortNodesByGroups.sortNodesByGroups(nodes, options, {
ignoreEslintDisabledNodes,
})
let sortedNodes = sortNodesExcludingEslintDisabled(false)
let sortedNodesExcludingEslintDisabled =
sortNodesExcludingEslintDisabled(true)
pairwise.pairwise(nodes, (left, right) => {
let indexOfLeft = sortedNodes.indexOf(left)
let indexOfRight = sortedNodes.indexOf(right)
let indexOfRightExcludingEslintDisabled =
sortedNodesExcludingEslintDisabled.indexOf(right)
if (
indexOfLeft < indexOfRight &&
indexOfLeft < indexOfRightExcludingEslintDisabled
) {
return
}
let leftNumber = getGroupNumber.getGroupNumber(options.groups, left)
let rightNumber = getGroupNumber.getGroupNumber(options.groups, right)
context.report({
fix: fixer =>
makeFixes.makeFixes({
sortedNodes: sortedNodesExcludingEslintDisabled,
sourceCode,
fixer,
nodes,
}),
data: {
rightGroup: right.group,
leftGroup: left.group,
right: right.name,
left: left.name,
},
messageId:
leftNumber === rightNumber
? 'unexpectedJSXPropsOrder'
: 'unexpectedJSXPropsGroupOrder',
node: right.node,
})
})
}
},
}),
meta: {
type: 'suggestion',
docs: {
description: 'Enforce sorted JSX props.',
},
fixable: 'code',
schema: [
{
type: 'object',
properties: {
type: {
description: 'Specifies the sorting method.',
type: 'string',
enum: ['alphabetical', 'natural', 'line-length'],
},
order: {
description:
'Determines whether the sorted items should be in ascending or descending order.',
type: 'string',
enum: ['asc', 'desc'],
},
matcher: {
description: 'Specifies the string matcher.',
type: 'string',
enum: ['minimatch', 'regex'],
},
ignoreCase: {
description:
'Controls whether sorting should be case-sensitive or not.',
type: 'boolean',
},
specialCharacters: {
description:
'Controls how special characters should be handled before sorting.',
type: 'string',
enum: ['remove', 'trim', 'keep'],
},
ignorePattern: {

@@ -63,38 +156,12 @@ description:

},
groups: {
description: 'Specifies the order of the groups.',
type: 'array',
items: {
oneOf: [
{
type: 'string',
},
{
type: 'array',
items: {
type: 'string',
},
},
],
},
},
customGroups: {
description: 'Specifies custom groups.',
type: 'object',
additionalProperties: {
oneOf: [
{
type: 'string',
},
{
type: 'array',
items: {
type: 'string',
},
},
],
},
},
specialCharacters: commonJsonSchemas.specialCharactersJsonSchema,
customGroups: commonJsonSchemas.customGroupsJsonSchema,
ignoreCase: commonJsonSchemas.ignoreCaseJsonSchema,
locales: commonJsonSchemas.localesJsonSchema,
groups: commonJsonSchemas.groupsJsonSchema,
order: commonJsonSchemas.orderJsonSchema,
type: commonJsonSchemas.typeJsonSchema,
},
additionalProperties: false,
type: 'object',
},

@@ -108,113 +175,13 @@ ],

},
docs: {
url: 'https://perfectionist.dev/rules/sort-jsx-props',
description: 'Enforce sorted JSX props.',
recommended: true,
},
type: 'suggestion',
fixable: 'code',
},
defaultOptions: [
{
type: 'alphabetical',
order: 'asc',
ignoreCase: true,
specialCharacters: 'keep',
matcher: 'minimatch',
ignorePattern: [],
groups: [],
customGroups: {},
},
],
create: context => ({
JSXElement: node => {
if (node.openingElement.attributes.length > 1) {
let settings = getSettings.getSettings(context.settings)
let options = complete.complete(context.options.at(0), settings, {
type: 'alphabetical',
ignorePattern: [],
ignoreCase: true,
specialCharacters: 'keep',
matcher: 'minimatch',
customGroups: {},
order: 'asc',
groups: [],
})
validateGroupsConfiguration.validateGroupsConfiguration(
options.groups,
['multiline', 'shorthand', 'unknown'],
Object.keys(options.customGroups),
)
let sourceCode = getSourceCode.getSourceCode(context)
let shouldIgnore = false
if (options.ignorePattern.length) {
let tagName = sourceCode.text.slice(...node.openingElement.name.range)
shouldIgnore = options.ignorePattern.some(pattern =>
matches.matches(tagName, pattern, options.matcher),
)
}
if (!shouldIgnore && node.openingElement.attributes.length > 1) {
let parts = node.openingElement.attributes.reduce(
(accumulator, attribute) => {
if (attribute.type === 'JSXSpreadAttribute') {
accumulator.push([])
return accumulator
}
let name =
attribute.name.type === 'JSXNamespacedName'
? `${attribute.name.namespace.name}:${attribute.name.name.name}`
: attribute.name.name
let { getGroup, defineGroup, setCustomGroups } =
useGroups.useGroups(options)
setCustomGroups(options.customGroups, name)
if (attribute.value === null) {
defineGroup('shorthand')
}
if (attribute.loc.start.line !== attribute.loc.end.line) {
defineGroup('multiline')
}
let jsxNode = {
size: rangeToDiff.rangeToDiff(attribute.range),
group: getGroup(),
node: attribute,
name,
}
accumulator.at(-1).push(jsxNode)
return accumulator
},
[[]],
)
for (let nodes of parts) {
let sortedNodes = sortNodesByGroups.sortNodesByGroups(
nodes,
options,
)
pairwise.pairwise(nodes, (left, right) => {
let indexOfLeft = sortedNodes.indexOf(left)
let indexOfRight = sortedNodes.indexOf(right)
if (indexOfLeft > indexOfRight) {
let leftNum = getGroupNumber.getGroupNumber(
options.groups,
left,
)
let rightNum = getGroupNumber.getGroupNumber(
options.groups,
right,
)
context.report({
messageId:
leftNum !== rightNum
? 'unexpectedJSXPropsGroupOrder'
: 'unexpectedJSXPropsOrder',
data: {
left: left.name,
leftGroup: left.group,
right: right.name,
rightGroup: right.group,
},
node: right.node,
fix: fixer =>
makeFixes.makeFixes(fixer, nodes, sortedNodes, sourceCode),
})
}
})
}
}
}
},
}),
defaultOptions: [defaultOptions],
name: 'sort-jsx-props',
})
module.exports = sortJsxProps
'use strict'
const commonJsonSchemas = require('../utils/common-json-schemas.js')
const getEslintDisabledLines = require('../utils/get-eslint-disabled-lines.js')
const isNodeEslintDisabled = require('../utils/is-node-eslint-disabled.js')
const isPartitionComment = require('../utils/is-partition-comment.js')

@@ -10,3 +13,3 @@ const getCommentsBefore = require('../utils/get-comments-before.js')

const getSettings = require('../utils/get-settings.js')
const isPositive = require('../utils/is-positive.js')
const isSortable = require('../utils/is-sortable.js')
const sortNodes = require('../utils/sort-nodes.js')

@@ -16,69 +19,158 @@ const makeFixes = require('../utils/make-fixes.js')

const pairwise = require('../utils/pairwise.js')
const compare = require('../utils/compare.js')
let defaultOptions = {
specialCharacters: 'keep',
partitionByComment: false,
partitionByNewLine: false,
type: 'alphabetical',
ignoreCase: true,
locales: 'en-US',
order: 'asc',
}
const sortMaps = createEslintRule.createEslintRule({
name: 'sort-maps',
create: context => ({
NewExpression: node => {
var _a, _b
if (
node.callee.type !== 'Identifier' ||
node.callee.name !== 'Map' ||
!node.arguments.length ||
((_a = node.arguments[0]) == null ? void 0 : _a.type) !==
'ArrayExpression'
) {
return
}
let [{ elements }] = node.arguments
if (!isSortable.isSortable(elements)) {
return
}
let settings = getSettings.getSettings(context.settings)
let options = complete.complete(
context.options.at(0),
settings,
defaultOptions,
)
let sourceCode = getSourceCode.getSourceCode(context)
let eslintDisabledLines = getEslintDisabledLines.getEslintDisabledLines({
ruleName: context.id,
sourceCode,
})
let parts = elements.reduce(
(accumulator, element) => {
if (element === null || element.type === 'SpreadElement') {
accumulator.push([])
} else {
accumulator.at(-1).push(element)
}
return accumulator
},
[[]],
)
for (let part of parts) {
let formattedMembers = [[]]
for (let element of part) {
let name
if (element.type === 'ArrayExpression') {
let [left] = element.elements
if (!left) {
name = `${left}`
} else if (left.type === 'Literal') {
name = left.raw
} else {
name = sourceCode.getText(left)
}
} else {
name = sourceCode.getText(element)
}
let lastSortingNode =
(_b = formattedMembers.at(-1)) == null ? void 0 : _b.at(-1)
let sortingNode = {
isEslintDisabled: isNodeEslintDisabled.isNodeEslintDisabled(
element,
eslintDisabledLines,
),
size: rangeToDiff.rangeToDiff(element, sourceCode),
node: element,
name,
}
if (
(options.partitionByComment &&
isPartitionComment.hasPartitionComment(
options.partitionByComment,
getCommentsBefore.getCommentsBefore(element, sourceCode),
)) ||
(options.partitionByNewLine &&
lastSortingNode &&
getLinesBetween.getLinesBetween(
sourceCode,
lastSortingNode,
sortingNode,
))
) {
formattedMembers.push([])
}
formattedMembers.at(-1).push(sortingNode)
}
for (let nodes of formattedMembers) {
let sortNodesExcludingEslintDisabled = ignoreEslintDisabledNodes =>
sortNodes.sortNodes(nodes, options, { ignoreEslintDisabledNodes })
let sortedNodes = sortNodesExcludingEslintDisabled(false)
let sortedNodesExcludingEslintDisabled =
sortNodesExcludingEslintDisabled(true)
pairwise.pairwise(nodes, (left, right) => {
let indexOfLeft = sortedNodes.indexOf(left)
let indexOfRight = sortedNodes.indexOf(right)
let indexOfRightExcludingEslintDisabled =
sortedNodesExcludingEslintDisabled.indexOf(right)
if (
indexOfLeft < indexOfRight &&
indexOfLeft < indexOfRightExcludingEslintDisabled
) {
return
}
context.report({
fix: fixer =>
makeFixes.makeFixes({
sortedNodes: sortedNodesExcludingEslintDisabled,
sourceCode,
options,
fixer,
nodes,
}),
data: {
right: toSingleLine.toSingleLine(right.name),
left: toSingleLine.toSingleLine(left.name),
},
messageId: 'unexpectedMapElementsOrder',
node: right.node,
})
})
}
}
},
}),
meta: {
type: 'suggestion',
docs: {
description: 'Enforce sorted Map elements.',
},
fixable: 'code',
schema: [
{
type: 'object',
properties: {
type: {
description: 'Specifies the sorting method.',
type: 'string',
enum: ['alphabetical', 'natural', 'line-length'],
},
order: {
description:
'Determines whether the sorted items should be in ascending or descending order.',
type: 'string',
enum: ['asc', 'desc'],
},
matcher: {
description: 'Specifies the string matcher.',
type: 'string',
enum: ['minimatch', 'regex'],
},
ignoreCase: {
description:
'Controls whether sorting should be case-sensitive or not.',
type: 'boolean',
},
specialCharacters: {
description:
'Controls how special characters should be handled before sorting.',
type: 'string',
enum: ['remove', 'trim', 'keep'],
},
partitionByComment: {
...commonJsonSchemas.partitionByCommentJsonSchema,
description:
'Allows you to use comments to separate the maps members into logical groups.',
anyOf: [
{
type: 'array',
items: {
type: 'string',
},
},
{
type: 'boolean',
},
{
type: 'string',
},
],
},
partitionByNewLine: {
description:
'Allows to use spaces to separate the nodes into logical groups.',
type: 'boolean',
},
partitionByNewLine: commonJsonSchemas.partitionByNewLineJsonSchema,
specialCharacters: commonJsonSchemas.specialCharactersJsonSchema,
ignoreCase: commonJsonSchemas.ignoreCaseJsonSchema,
locales: commonJsonSchemas.localesJsonSchema,
order: commonJsonSchemas.orderJsonSchema,
type: commonJsonSchemas.typeJsonSchema,
},
additionalProperties: false,
type: 'object',
},
],
docs: {
url: 'https://perfectionist.dev/rules/sort-maps',
description: 'Enforce sorted Map elements.',
recommended: true,
},
messages: {

@@ -88,121 +180,8 @@ unexpectedMapElementsOrder:

},
type: 'suggestion',
fixable: 'code',
},
defaultOptions: [
{
type: 'alphabetical',
order: 'asc',
ignoreCase: true,
specialCharacters: 'keep',
matcher: 'minimatch',
partitionByComment: false,
partitionByNewLine: false,
},
],
create: context => ({
NewExpression: node => {
var _a, _b
if (
node.callee.type === 'Identifier' &&
node.callee.name === 'Map' &&
node.arguments.length &&
((_a = node.arguments[0]) == null ? void 0 : _a.type) ===
'ArrayExpression'
) {
let [{ elements }] = node.arguments
if (elements.length > 1) {
let settings = getSettings.getSettings(context.settings)
let options = complete.complete(context.options.at(0), settings, {
type: 'alphabetical',
ignoreCase: true,
specialCharacters: 'keep',
order: 'asc',
matcher: 'minimatch',
partitionByComment: false,
partitionByNewLine: false,
})
let sourceCode = getSourceCode.getSourceCode(context)
let partitionComment = options.partitionByComment
let parts = elements.reduce(
(accumulator, element) => {
if (element === null || element.type === 'SpreadElement') {
accumulator.push([])
} else {
accumulator.at(-1).push(element)
}
return accumulator
},
[[]],
)
for (let part of parts) {
let formattedMembers = [[]]
for (let element of part) {
let name
if (element.type === 'ArrayExpression') {
let [left] = element.elements
if (!left) {
name = `${left}`
} else if (left.type === 'Literal') {
name = left.raw
} else {
name = sourceCode.text.slice(...left.range)
}
} else {
name = sourceCode.text.slice(...element.range)
}
let lastSortingNode =
(_b = formattedMembers.at(-1)) == null ? void 0 : _b.at(-1)
let sortingNode = {
size: rangeToDiff.rangeToDiff(element.range),
node: element,
name,
}
if (
(partitionComment &&
isPartitionComment.hasPartitionComment(
partitionComment,
getCommentsBefore.getCommentsBefore(element, sourceCode),
options.matcher,
)) ||
(options.partitionByNewLine &&
lastSortingNode &&
getLinesBetween.getLinesBetween(
sourceCode,
lastSortingNode,
sortingNode,
))
) {
formattedMembers.push([])
}
formattedMembers.at(-1).push(sortingNode)
}
for (let nodes of formattedMembers) {
pairwise.pairwise(nodes, (left, right) => {
if (
isPositive.isPositive(compare.compare(left, right, options))
) {
context.report({
messageId: 'unexpectedMapElementsOrder',
data: {
left: toSingleLine.toSingleLine(left.name),
right: toSingleLine.toSingleLine(right.name),
},
node: right.node,
fix: fixer =>
makeFixes.makeFixes(
fixer,
nodes,
sortNodes.sortNodes(nodes, options),
sourceCode,
options,
),
})
}
})
}
}
}
}
},
}),
defaultOptions: [defaultOptions],
name: 'sort-maps',
})
module.exports = sortMaps
'use strict'
const commonJsonSchemas = require('../utils/common-json-schemas.js')
const getEslintDisabledLines = require('../utils/get-eslint-disabled-lines.js')
const isNodeEslintDisabled = require('../utils/is-node-eslint-disabled.js')
const isPartitionComment = require('../utils/is-partition-comment.js')

@@ -6,7 +9,6 @@ const getCommentsBefore = require('../utils/get-comments-before.js')

const getLinesBetween = require('../utils/get-lines-between.js')
const getGroupNumber = require('../utils/get-group-number.js')
const getSourceCode = require('../utils/get-source-code.js')
const rangeToDiff = require('../utils/range-to-diff.js')
const getSettings = require('../utils/get-settings.js')
const isPositive = require('../utils/is-positive.js')
const isSortable = require('../utils/is-sortable.js')
const sortNodes = require('../utils/sort-nodes.js')

@@ -16,74 +18,154 @@ const makeFixes = require('../utils/make-fixes.js')

const pairwise = require('../utils/pairwise.js')
const compare = require('../utils/compare.js')
let defaultOptions = {
specialCharacters: 'keep',
partitionByNewLine: false,
partitionByComment: false,
type: 'alphabetical',
groupKind: 'mixed',
ignoreCase: true,
locales: 'en-US',
order: 'asc',
}
const sortNamedExports = createEslintRule.createEslintRule({
name: 'sort-named-exports',
create: context => ({
ExportNamedDeclaration: node => {
var _a
if (!isSortable.isSortable(node.specifiers)) {
return
}
let settings = getSettings.getSettings(context.settings)
let options = complete.complete(
context.options.at(0),
settings,
defaultOptions,
)
let sourceCode = getSourceCode.getSourceCode(context)
let eslintDisabledLines = getEslintDisabledLines.getEslintDisabledLines({
ruleName: context.id,
sourceCode,
})
let formattedMembers = [[]]
for (let specifier of node.specifiers) {
let groupKind = specifier.exportKind === 'type' ? 'type' : 'value'
let name
if (specifier.exported.type === 'Identifier') {
;({ name } = specifier.exported)
} else {
name = specifier.exported.value
}
let lastSortingNode =
(_a = formattedMembers.at(-1)) == null ? void 0 : _a.at(-1)
let sortingNode = {
isEslintDisabled: isNodeEslintDisabled.isNodeEslintDisabled(
specifier,
eslintDisabledLines,
),
size: rangeToDiff.rangeToDiff(specifier, sourceCode),
node: specifier,
groupKind,
name,
}
if (
(options.partitionByComment &&
isPartitionComment.hasPartitionComment(
options.partitionByComment,
getCommentsBefore.getCommentsBefore(specifier, sourceCode),
)) ||
(options.partitionByNewLine &&
lastSortingNode &&
getLinesBetween.getLinesBetween(
sourceCode,
lastSortingNode,
sortingNode,
))
) {
formattedMembers.push([])
}
formattedMembers.at(-1).push(sortingNode)
}
let groupKindOrder
if (options.groupKind === 'values-first') {
groupKindOrder = ['value', 'type']
} else if (options.groupKind === 'types-first') {
groupKindOrder = ['type', 'value']
} else {
groupKindOrder = ['any']
}
for (let nodes of formattedMembers) {
let filteredGroupKindNodes = groupKindOrder.map(groupKind =>
nodes.filter(
currentNode =>
groupKind === 'any' || currentNode.groupKind === groupKind,
),
)
let sortNodesExcludingEslintDisabled = ignoreEslintDisabledNodes =>
filteredGroupKindNodes.flatMap(groupedNodes =>
sortNodes.sortNodes(groupedNodes, options, {
ignoreEslintDisabledNodes,
}),
)
let sortedNodes = sortNodesExcludingEslintDisabled(false)
let sortedNodesExcludingEslintDisabled =
sortNodesExcludingEslintDisabled(true)
pairwise.pairwise(nodes, (left, right) => {
let indexOfLeft = sortedNodes.indexOf(left)
let indexOfRight = sortedNodes.indexOf(right)
let indexOfRightExcludingEslintDisabled =
sortedNodesExcludingEslintDisabled.indexOf(right)
if (
indexOfLeft < indexOfRight &&
indexOfLeft < indexOfRightExcludingEslintDisabled
) {
return
}
context.report({
fix: fixer =>
makeFixes.makeFixes({
sortedNodes: sortedNodesExcludingEslintDisabled,
sourceCode,
options,
fixer,
nodes,
}),
data: {
right: right.name,
left: left.name,
},
messageId: 'unexpectedNamedExportsOrder',
node: right.node,
})
})
}
},
}),
meta: {
type: 'suggestion',
docs: {
description: 'Enforce sorted named exports.',
},
fixable: 'code',
schema: [
{
type: 'object',
properties: {
type: {
description: 'Specifies the sorting method.',
type: 'string',
enum: ['alphabetical', 'natural', 'line-length'],
},
order: {
partitionByComment: {
...commonJsonSchemas.partitionByCommentJsonSchema,
description:
'Determines whether the sorted items should be in ascending or descending order.',
type: 'string',
enum: ['asc', 'desc'],
'Allows you to use comments to separate the named exports members into logical groups.',
},
matcher: {
description: 'Specifies the string matcher.',
type: 'string',
enum: ['minimatch', 'regex'],
},
ignoreCase: {
description:
'Controls whether sorting should be case-sensitive or not.',
type: 'boolean',
},
specialCharacters: {
description:
'Controls how special characters should be handled before sorting.',
type: 'string',
enum: ['remove', 'trim', 'keep'],
},
groupKind: {
enum: ['mixed', 'values-first', 'types-first'],
description: 'Specifies top-level groups.',
enum: ['mixed', 'values-first', 'types-first'],
type: 'string',
},
partitionByComment: {
description:
'Allows you to use comments to separate the named exports members into logical groups.',
anyOf: [
{
type: 'array',
items: {
type: 'string',
},
},
{
type: 'boolean',
},
{
type: 'string',
},
],
},
partitionByNewLine: {
description:
'Allows to use spaces to separate the nodes into logical groups.',
type: 'boolean',
},
partitionByNewLine: commonJsonSchemas.partitionByNewLineJsonSchema,
specialCharacters: commonJsonSchemas.specialCharactersJsonSchema,
ignoreCase: commonJsonSchemas.ignoreCaseJsonSchema,
locales: commonJsonSchemas.localesJsonSchema,
order: commonJsonSchemas.orderJsonSchema,
type: commonJsonSchemas.typeJsonSchema,
},
additionalProperties: false,
type: 'object',
},
],
docs: {
url: 'https://perfectionist.dev/rules/sort-named-exports',
description: 'Enforce sorted named exports.',
recommended: true,
},
messages: {

@@ -93,112 +175,8 @@ unexpectedNamedExportsOrder:

},
type: 'suggestion',
fixable: 'code',
},
defaultOptions: [
{
type: 'alphabetical',
order: 'asc',
ignoreCase: true,
specialCharacters: 'keep',
matcher: 'minimatch',
partitionByNewLine: false,
partitionByComment: false,
groupKind: 'mixed',
},
],
create: context => ({
ExportNamedDeclaration: node => {
var _a
if (node.specifiers.length > 1) {
let settings = getSettings.getSettings(context.settings)
let options = complete.complete(context.options.at(0), settings, {
type: 'alphabetical',
groupKind: 'mixed',
ignoreCase: true,
specialCharacters: 'keep',
matcher: 'minimatch',
partitionByNewLine: false,
partitionByComment: false,
order: 'asc',
})
let sourceCode = getSourceCode.getSourceCode(context)
let partitionComment = options.partitionByComment
let formattedMembers = [[]]
for (let specifier of node.specifiers) {
let group
if (specifier.exportKind === 'type') {
group = 'type'
} else {
group = 'value'
}
let lastSortingNode =
(_a = formattedMembers.at(-1)) == null ? void 0 : _a.at(-1)
let sortingNode = {
size: rangeToDiff.rangeToDiff(specifier.range),
name: specifier.local.name,
node: specifier,
group,
}
if (
(partitionComment &&
isPartitionComment.hasPartitionComment(
partitionComment,
getCommentsBefore.getCommentsBefore(specifier, sourceCode),
options.matcher,
)) ||
(options.partitionByNewLine &&
lastSortingNode &&
getLinesBetween.getLinesBetween(
sourceCode,
lastSortingNode,
sortingNode,
))
) {
formattedMembers.push([])
}
formattedMembers.at(-1).push(sortingNode)
}
let shouldGroupByKind = options.groupKind !== 'mixed'
let groupKindOrder =
options.groupKind === 'values-first'
? ['value', 'type']
: ['type', 'value']
for (let nodes of formattedMembers) {
pairwise.pairwise(nodes, (left, right) => {
let leftNum = getGroupNumber.getGroupNumber(groupKindOrder, left)
let rightNum = getGroupNumber.getGroupNumber(groupKindOrder, right)
if (
(shouldGroupByKind && leftNum > rightNum) ||
((!shouldGroupByKind || leftNum === rightNum) &&
isPositive.isPositive(compare.compare(left, right, options)))
) {
let sortedNodes = shouldGroupByKind
? groupKindOrder
.map(group => nodes.filter(n => n.group === group))
.map(groupedNodes =>
sortNodes.sortNodes(groupedNodes, options),
)
.flat()
: sortNodes.sortNodes(nodes, options)
context.report({
messageId: 'unexpectedNamedExportsOrder',
data: {
left: left.name,
right: right.name,
},
node: right.node,
fix: fixer =>
makeFixes.makeFixes(
fixer,
nodes,
sortedNodes,
sourceCode,
options,
),
})
}
})
}
}
},
}),
defaultOptions: [defaultOptions],
name: 'sort-named-exports',
})
module.exports = sortNamedExports
'use strict'
const commonJsonSchemas = require('../utils/common-json-schemas.js')
const getEslintDisabledLines = require('../utils/get-eslint-disabled-lines.js')
const isNodeEslintDisabled = require('../utils/is-node-eslint-disabled.js')
const isPartitionComment = require('../utils/is-partition-comment.js')

@@ -6,7 +9,6 @@ const getCommentsBefore = require('../utils/get-comments-before.js')

const getLinesBetween = require('../utils/get-lines-between.js')
const getGroupNumber = require('../utils/get-group-number.js')
const getSourceCode = require('../utils/get-source-code.js')
const rangeToDiff = require('../utils/range-to-diff.js')
const getSettings = require('../utils/get-settings.js')
const isPositive = require('../utils/is-positive.js')
const isSortable = require('../utils/is-sortable.js')
const sortNodes = require('../utils/sort-nodes.js')

@@ -16,42 +18,147 @@ const makeFixes = require('../utils/make-fixes.js')

const pairwise = require('../utils/pairwise.js')
const compare = require('../utils/compare.js')
let defaultOptions = {
specialCharacters: 'keep',
partitionByNewLine: false,
partitionByComment: false,
type: 'alphabetical',
ignoreAlias: false,
groupKind: 'mixed',
ignoreCase: true,
locales: 'en-US',
order: 'asc',
}
const sortNamedImports = createEslintRule.createEslintRule({
name: 'sort-named-imports',
create: context => ({
ImportDeclaration: node => {
var _a
let specifiers = node.specifiers.filter(
({ type }) => type === 'ImportSpecifier',
)
if (!isSortable.isSortable(specifiers)) {
return
}
let settings = getSettings.getSettings(context.settings)
let options = complete.complete(
context.options.at(0),
settings,
defaultOptions,
)
let sourceCode = getSourceCode.getSourceCode(context)
let eslintDisabledLines = getEslintDisabledLines.getEslintDisabledLines({
ruleName: context.id,
sourceCode,
})
let formattedMembers = [[]]
for (let specifier of specifiers) {
let { name } = specifier.local
if (specifier.type === 'ImportSpecifier' && options.ignoreAlias) {
if (specifier.imported.type === 'Identifier') {
;({ name } = specifier.imported)
} else {
name = specifier.imported.value
}
}
let lastSortingNode =
(_a = formattedMembers.at(-1)) == null ? void 0 : _a.at(-1)
let sortingNode = {
groupKind:
specifier.type === 'ImportSpecifier' &&
specifier.importKind === 'type'
? 'type'
: 'value',
isEslintDisabled: isNodeEslintDisabled.isNodeEslintDisabled(
specifier,
eslintDisabledLines,
),
size: rangeToDiff.rangeToDiff(specifier, sourceCode),
node: specifier,
name,
}
if (
(options.partitionByComment &&
isPartitionComment.hasPartitionComment(
options.partitionByComment,
getCommentsBefore.getCommentsBefore(specifier, sourceCode),
)) ||
(options.partitionByNewLine &&
lastSortingNode &&
getLinesBetween.getLinesBetween(
sourceCode,
lastSortingNode,
sortingNode,
))
) {
formattedMembers.push([])
}
formattedMembers.at(-1).push(sortingNode)
}
let groupKindOrder
if (options.groupKind === 'values-first') {
groupKindOrder = ['value', 'type']
} else if (options.groupKind === 'types-first') {
groupKindOrder = ['type', 'value']
} else {
groupKindOrder = ['any']
}
for (let nodes of formattedMembers) {
let filteredGroupKindNodes = groupKindOrder.map(groupKind =>
nodes.filter(
currentNode =>
groupKind === 'any' || currentNode.groupKind === groupKind,
),
)
let sortNodesExcludingEslintDisabled = ignoreEslintDisabledNodes =>
filteredGroupKindNodes.flatMap(groupedNodes =>
sortNodes.sortNodes(groupedNodes, options, {
ignoreEslintDisabledNodes,
}),
)
let sortedNodes = sortNodesExcludingEslintDisabled(false)
let sortedNodesExcludingEslintDisabled =
sortNodesExcludingEslintDisabled(true)
pairwise.pairwise(nodes, (left, right) => {
let indexOfLeft = sortedNodes.indexOf(left)
let indexOfRight = sortedNodes.indexOf(right)
let indexOfRightExcludingEslintDisabled =
sortedNodesExcludingEslintDisabled.indexOf(right)
if (
indexOfLeft < indexOfRight &&
indexOfLeft < indexOfRightExcludingEslintDisabled
) {
return
}
context.report({
fix: fixer =>
makeFixes.makeFixes({
sortedNodes: sortedNodesExcludingEslintDisabled,
sourceCode,
options,
fixer,
nodes,
}),
data: {
right: right.name,
left: left.name,
},
messageId: 'unexpectedNamedImportsOrder',
node: right.node,
})
})
}
},
}),
meta: {
type: 'suggestion',
docs: {
description: 'Enforce sorted named imports.',
},
fixable: 'code',
schema: [
{
type: 'object',
properties: {
type: {
description: 'Specifies the sorting method.',
type: 'string',
enum: ['alphabetical', 'natural', 'line-length'],
},
order: {
partitionByComment: {
...commonJsonSchemas.partitionByCommentJsonSchema,
description:
'Determines whether the sorted items should be in ascending or descending order.',
type: 'string',
enum: ['asc', 'desc'],
'Allows you to use comments to separate the named imports members into logical groups.',
},
matcher: {
description: 'Specifies the string matcher.',
groupKind: {
enum: ['mixed', 'values-first', 'types-first'],
description: 'Specifies top-level groups.',
type: 'string',
enum: ['minimatch', 'regex'],
},
ignoreCase: {
description:
'Controls whether sorting should be case-sensitive or not.',
type: 'boolean',
},
specialCharacters: {
description:
'Controls how special characters should be handled before sorting.',
type: 'string',
enum: ['remove', 'trim', 'keep'],
},
ignoreAlias: {

@@ -61,34 +168,18 @@ description: 'Controls whether to ignore alias names.',

},
groupKind: {
description: 'Specifies top-level groups.',
enum: ['mixed', 'values-first', 'types-first'],
type: 'string',
},
partitionByComment: {
description:
'Allows you to use comments to separate the named imports members into logical groups.',
anyOf: [
{
type: 'array',
items: {
type: 'string',
},
},
{
type: 'boolean',
},
{
type: 'string',
},
],
},
partitionByNewLine: {
description:
'Allows to use spaces to separate the nodes into logical groups.',
type: 'boolean',
},
partitionByNewLine: commonJsonSchemas.partitionByNewLineJsonSchema,
specialCharacters: commonJsonSchemas.specialCharactersJsonSchema,
ignoreCase: commonJsonSchemas.ignoreCaseJsonSchema,
locales: commonJsonSchemas.localesJsonSchema,
order: commonJsonSchemas.orderJsonSchema,
type: commonJsonSchemas.typeJsonSchema,
},
additionalProperties: false,
type: 'object',
},
],
docs: {
url: 'https://perfectionist.dev/rules/sort-named-imports',
description: 'Enforce sorted named imports.',
recommended: true,
},
messages: {

@@ -98,123 +189,8 @@ unexpectedNamedImportsOrder:

},
type: 'suggestion',
fixable: 'code',
},
defaultOptions: [
{
type: 'alphabetical',
order: 'asc',
ignoreAlias: false,
ignoreCase: true,
specialCharacters: 'keep',
partitionByNewLine: false,
partitionByComment: false,
groupKind: 'mixed',
},
],
create: context => ({
ImportDeclaration: node => {
var _a
let specifiers = node.specifiers.filter(
({ type }) => type === 'ImportSpecifier',
)
if (specifiers.length > 1) {
let settings = getSettings.getSettings(context.settings)
let options = complete.complete(context.options.at(0), settings, {
type: 'alphabetical',
ignoreAlias: false,
groupKind: 'mixed',
ignoreCase: true,
specialCharacters: 'keep',
matcher: 'minimatch',
partitionByNewLine: false,
partitionByComment: false,
order: 'asc',
})
let sourceCode = getSourceCode.getSourceCode(context)
let partitionComment = options.partitionByComment
let formattedMembers = [[]]
for (let specifier of specifiers) {
let group
let { name } = specifier.local
if (specifier.type === 'ImportSpecifier' && options.ignoreAlias) {
;({ name } = specifier.imported)
}
if (
specifier.type === 'ImportSpecifier' &&
specifier.importKind === 'type'
) {
group = 'type'
} else {
group = 'value'
}
let lastSortingNode =
(_a = formattedMembers.at(-1)) == null ? void 0 : _a.at(-1)
let sortingNode = {
size: rangeToDiff.rangeToDiff(specifier.range),
node: specifier,
group,
name,
}
if (
(partitionComment &&
isPartitionComment.hasPartitionComment(
partitionComment,
getCommentsBefore.getCommentsBefore(specifier, sourceCode),
options.matcher,
)) ||
(options.partitionByNewLine &&
lastSortingNode &&
getLinesBetween.getLinesBetween(
sourceCode,
lastSortingNode,
sortingNode,
))
) {
formattedMembers.push([])
}
formattedMembers.at(-1).push(sortingNode)
}
let shouldGroupByKind = options.groupKind !== 'mixed'
let groupKindOrder =
options.groupKind === 'values-first'
? ['value', 'type']
: ['type', 'value']
for (let nodes of formattedMembers) {
pairwise.pairwise(nodes, (left, right) => {
let leftNum = getGroupNumber.getGroupNumber(groupKindOrder, left)
let rightNum = getGroupNumber.getGroupNumber(groupKindOrder, right)
if (
(shouldGroupByKind && leftNum > rightNum) ||
((!shouldGroupByKind || leftNum === rightNum) &&
isPositive.isPositive(compare.compare(left, right, options)))
) {
let sortedNodes = shouldGroupByKind
? groupKindOrder
.map(group => nodes.filter(n => n.group === group))
.map(groupedNodes =>
sortNodes.sortNodes(groupedNodes, options),
)
.flat()
: sortNodes.sortNodes(nodes, options)
context.report({
messageId: 'unexpectedNamedImportsOrder',
data: {
left: left.name,
right: right.name,
},
node: right.node,
fix: fixer =>
makeFixes.makeFixes(
fixer,
nodes,
sortedNodes,
sourceCode,
options,
),
})
}
})
}
}
},
}),
defaultOptions: [defaultOptions],
name: 'sort-named-imports',
})
module.exports = sortNamedImports
'use strict'
const commonJsonSchemas = require('../utils/common-json-schemas.js')
const validateNewlinesAndPartitionConfiguration = require('../utils/validate-newlines-and-partition-configuration.js')
const validateGroupsConfiguration = require('../utils/validate-groups-configuration.js')
const getEslintDisabledLines = require('../utils/get-eslint-disabled-lines.js')
const isNodeEslintDisabled = require('../utils/is-node-eslint-disabled.js')
const isPartitionComment = require('../utils/is-partition-comment.js')
const sortNodesByGroups = require('../utils/sort-nodes-by-groups.js')
const getCommentsBefore = require('../utils/get-comments-before.js')
const makeNewlinesFixes = require('../utils/make-newlines-fixes.js')
const getNewlinesErrors = require('../utils/get-newlines-errors.js')
const createEslintRule = require('../utils/create-eslint-rule.js')
const isMemberOptional = require('../utils/is-member-optional.js')
const getLinesBetween = require('../utils/get-lines-between.js')

@@ -13,2 +20,3 @@ const getGroupNumber = require('../utils/get-group-number.js')

const getSettings = require('../utils/get-settings.js')
const isSortable = require('../utils/is-sortable.js')
const makeFixes = require('../utils/make-fixes.js')

@@ -18,105 +26,236 @@ const useGroups = require('../utils/use-groups.js')

const pairwise = require('../utils/pairwise.js')
let defaultOptions = {
partitionByComment: false,
partitionByNewLine: false,
newlinesBetween: 'ignore',
specialCharacters: 'keep',
type: 'alphabetical',
groupKind: 'mixed',
ignoreCase: true,
customGroups: {},
locales: 'en-US',
order: 'asc',
groups: [],
}
const sortObjectTypes = createEslintRule.createEslintRule({
name: 'sort-object-types',
create: context => ({
TSTypeLiteral: node => {
if (!isSortable.isSortable(node.members)) {
return
}
let settings = getSettings.getSettings(context.settings)
let options = complete.complete(
context.options.at(0),
settings,
defaultOptions,
)
validateGroupsConfiguration.validateGroupsConfiguration(
options.groups,
['multiline', 'method', 'unknown'],
Object.keys(options.customGroups),
)
validateNewlinesAndPartitionConfiguration.validateNewlinesAndPartitionConfiguration(
options,
)
let sourceCode = getSourceCode.getSourceCode(context)
let eslintDisabledLines = getEslintDisabledLines.getEslintDisabledLines({
ruleName: context.id,
sourceCode,
})
let formattedMembers = node.members.reduce(
(accumulator, member) => {
var _a, _b, _c, _d, _e
let name
let lastSortingNode =
(_a = accumulator.at(-1)) == null ? void 0 : _a.at(-1)
let { setCustomGroups, defineGroup, getGroup } =
useGroups.useGroups(options)
let formatName = value => value.replace(/[,;]$/u, '')
if (member.type === 'TSPropertySignature') {
if (member.key.type === 'Identifier') {
;({ name } = member.key)
} else if (member.key.type === 'Literal') {
name = `${member.key.value}`
} else {
name = sourceCode.text.slice(
member.range.at(0),
((_b = member.typeAnnotation) == null
? void 0
: _b.range.at(0)) ?? member.range.at(1),
)
}
} else if (member.type === 'TSIndexSignature') {
let endIndex =
((_c = member.typeAnnotation) == null
? void 0
: _c.range.at(0)) ?? member.range.at(1)
name = formatName(
sourceCode.text.slice(member.range.at(0), endIndex),
)
} else {
name = formatName(
sourceCode.text.slice(member.range.at(0), member.range.at(1)),
)
}
setCustomGroups(options.customGroups, name)
if (
member.type === 'TSMethodSignature' ||
(member.type === 'TSPropertySignature' &&
((_d = member.typeAnnotation) == null
? void 0
: _d.typeAnnotation.type) === 'TSFunctionType')
) {
defineGroup('method')
}
if (member.loc.start.line !== member.loc.end.line) {
defineGroup('multiline')
}
let sortingNode = {
isEslintDisabled: isNodeEslintDisabled.isNodeEslintDisabled(
member,
eslintDisabledLines,
),
groupKind: isMemberOptional.isMemberOptional(member)
? 'optional'
: 'required',
size: rangeToDiff.rangeToDiff(member, sourceCode),
addSafetySemicolonWhenInline: true,
group: getGroup(),
node: member,
name,
}
if (
(options.partitionByComment &&
isPartitionComment.hasPartitionComment(
options.partitionByComment,
getCommentsBefore.getCommentsBefore(member, sourceCode),
)) ||
(options.partitionByNewLine &&
lastSortingNode &&
getLinesBetween.getLinesBetween(
sourceCode,
lastSortingNode,
sortingNode,
))
) {
accumulator.push([])
}
;(_e = accumulator.at(-1)) == null ? void 0 : _e.push(sortingNode)
return accumulator
},
[[]],
)
let groupKindOrder
if (options.groupKind === 'required-first') {
groupKindOrder = ['required', 'optional']
} else if (options.groupKind === 'optional-first') {
groupKindOrder = ['optional', 'required']
} else {
groupKindOrder = ['any']
}
for (let nodes of formattedMembers) {
let filteredGroupKindNodes = groupKindOrder.map(groupKind =>
nodes.filter(
currentNode =>
groupKind === 'any' || currentNode.groupKind === groupKind,
),
)
let sortNodesExcludingEslintDisabled = ignoreEslintDisabledNodes =>
filteredGroupKindNodes.flatMap(groupedNodes =>
sortNodesByGroups.sortNodesByGroups(groupedNodes, options, {
ignoreEslintDisabledNodes,
}),
)
let sortedNodes = sortNodesExcludingEslintDisabled(false)
let sortedNodesExcludingEslintDisabled =
sortNodesExcludingEslintDisabled(true)
pairwise.pairwise(nodes, (left, right) => {
let leftNumber = getGroupNumber.getGroupNumber(options.groups, left)
let rightNumber = getGroupNumber.getGroupNumber(options.groups, right)
let indexOfLeft = sortedNodes.indexOf(left)
let indexOfRight = sortedNodes.indexOf(right)
let indexOfRightExcludingEslintDisabled =
sortedNodesExcludingEslintDisabled.indexOf(right)
let messageIds = []
if (
indexOfLeft > indexOfRight ||
indexOfLeft >= indexOfRightExcludingEslintDisabled
) {
messageIds.push(
leftNumber === rightNumber
? 'unexpectedObjectTypesOrder'
: 'unexpectedObjectTypesGroupOrder',
)
}
messageIds = [
...messageIds,
...getNewlinesErrors.getNewlinesErrors({
missedSpacingError: 'missedSpacingBetweenObjectTypeMembers',
extraSpacingError: 'extraSpacingBetweenObjectTypeMembers',
rightNum: rightNumber,
leftNum: leftNumber,
sourceCode,
options,
right,
left,
}),
]
for (let messageId of messageIds) {
context.report({
fix: fixer => [
...makeFixes.makeFixes({
sortedNodes: sortedNodesExcludingEslintDisabled,
sourceCode,
options,
fixer,
nodes,
}),
...makeNewlinesFixes.makeNewlinesFixes({
sortedNodes: sortedNodesExcludingEslintDisabled,
sourceCode,
options,
fixer,
nodes,
}),
],
data: {
right: toSingleLine.toSingleLine(right.name),
left: toSingleLine.toSingleLine(left.name),
rightGroup: right.group,
leftGroup: left.group,
},
node: right.node,
messageId,
})
}
})
}
},
}),
meta: {
type: 'suggestion',
docs: {
description: 'Enforce sorted object types.',
},
fixable: 'code',
schema: [
{
type: 'object',
properties: {
type: {
description: 'Specifies the sorting method.',
type: 'string',
enum: ['alphabetical', 'natural', 'line-length'],
},
order: {
description:
'Determines whether the sorted items should be in ascending or descending order.',
type: 'string',
enum: ['asc', 'desc'],
},
matcher: {
description: 'Specifies the string matcher.',
type: 'string',
enum: ['minimatch', 'regex'],
},
ignoreCase: {
description:
'Controls whether sorting should be case-sensitive or not.',
type: 'boolean',
},
specialCharacters: {
description:
'Controls how special characters should be handled before sorting.',
type: 'string',
enum: ['remove', 'trim', 'keep'],
},
partitionByComment: {
...commonJsonSchemas.partitionByCommentJsonSchema,
description:
'Allows you to use comments to separate the type members into logical groups.',
anyOf: [
{
type: 'array',
items: {
type: 'string',
},
},
{
type: 'boolean',
},
{
type: 'string',
},
],
},
partitionByNewLine: {
description:
'Allows to use spaces to separate the nodes into logical groups.',
type: 'boolean',
},
groupKind: {
enum: ['mixed', 'required-first', 'optional-first'],
description: 'Specifies top-level groups.',
type: 'string',
enum: ['mixed', 'required-first', 'optional-first'],
},
groups: {
description: 'Specifies the order of the groups.',
type: 'array',
items: {
oneOf: [
{
type: 'string',
},
{
type: 'array',
items: {
type: 'string',
},
},
],
},
},
customGroups: {
description: 'Specifies custom groups.',
type: 'object',
additionalProperties: {
oneOf: [
{
type: 'string',
},
{
type: 'array',
items: {
type: 'string',
},
},
],
},
},
partitionByNewLine: commonJsonSchemas.partitionByNewLineJsonSchema,
specialCharacters: commonJsonSchemas.specialCharactersJsonSchema,
newlinesBetween: commonJsonSchemas.newlinesBetweenJsonSchema,
customGroups: commonJsonSchemas.customGroupsJsonSchema,
ignoreCase: commonJsonSchemas.ignoreCaseJsonSchema,
locales: commonJsonSchemas.localesJsonSchema,
groups: commonJsonSchemas.groupsJsonSchema,
order: commonJsonSchemas.orderJsonSchema,
type: commonJsonSchemas.typeJsonSchema,
},
additionalProperties: false,
type: 'object',
},

@@ -127,192 +266,20 @@ ],

'Expected "{{right}}" ({{rightGroup}}) to come before "{{left}}" ({{leftGroup}}).',
missedSpacingBetweenObjectTypeMembers:
'Missed spacing between "{{left}}" and "{{right}}" types.',
extraSpacingBetweenObjectTypeMembers:
'Extra spacing between "{{left}}" and "{{right}}" types.',
unexpectedObjectTypesOrder:
'Expected "{{right}}" to come before "{{left}}".',
},
docs: {
url: 'https://perfectionist.dev/rules/sort-object-types',
description: 'Enforce sorted object types.',
recommended: true,
},
type: 'suggestion',
fixable: 'code',
},
defaultOptions: [
{
type: 'alphabetical',
order: 'asc',
ignoreCase: true,
specialCharacters: 'keep',
matcher: 'minimatch',
partitionByComment: false,
partitionByNewLine: false,
groupKind: 'mixed',
groups: [],
customGroups: {},
},
],
create: context => ({
TSTypeLiteral: node => {
if (node.members.length > 1) {
let settings = getSettings.getSettings(context.settings)
let options = complete.complete(context.options.at(0), settings, {
partitionByComment: false,
partitionByNewLine: false,
type: 'alphabetical',
groupKind: 'mixed',
matcher: 'minimatch',
ignoreCase: true,
specialCharacters: 'keep',
customGroups: {},
order: 'asc',
groups: [],
})
validateGroupsConfiguration.validateGroupsConfiguration(
options.groups,
['multiline', 'method', 'unknown'],
Object.keys(options.customGroups),
)
let sourceCode = getSourceCode.getSourceCode(context)
let partitionComment = options.partitionByComment
let formattedMembers = node.members.reduce(
(accumulator, member) => {
var _a, _b, _c, _d, _e
let name
let raw = sourceCode.text.slice(
member.range.at(0),
member.range.at(1),
)
let lastSortingNode =
(_a = accumulator.at(-1)) == null ? void 0 : _a.at(-1)
let { getGroup, defineGroup, setCustomGroups } =
useGroups.useGroups(options)
let formatName = value => value.replace(/([,;])$/, '')
if (member.type === 'TSPropertySignature') {
if (member.key.type === 'Identifier') {
;({ name } = member.key)
} else if (member.key.type === 'Literal') {
name = `${member.key.value}`
} else {
name = sourceCode.text.slice(
member.range.at(0),
(_b = member.typeAnnotation) == null
? void 0
: _b.range.at(0),
)
}
} else if (member.type === 'TSIndexSignature') {
let endIndex =
((_c = member.typeAnnotation) == null
? void 0
: _c.range.at(0)) ?? member.range.at(1)
name = formatName(
sourceCode.text.slice(member.range.at(0), endIndex),
)
} else {
name = formatName(
sourceCode.text.slice(member.range.at(0), member.range.at(1)),
)
}
setCustomGroups(options.customGroups, name)
if (
member.type === 'TSMethodSignature' ||
(member.type === 'TSPropertySignature' &&
((_d = member.typeAnnotation) == null
? void 0
: _d.typeAnnotation.type) === 'TSFunctionType')
) {
defineGroup('method')
}
if (member.loc.start.line !== member.loc.end.line) {
defineGroup('multiline')
}
let endsWithComma = raw.endsWith(';') || raw.endsWith(',')
let endSize = endsWithComma ? 1 : 0
let sortingNode = {
size: rangeToDiff.rangeToDiff(member.range) - endSize,
group: getGroup(),
node: member,
name,
}
if (
(partitionComment &&
isPartitionComment.hasPartitionComment(
partitionComment,
getCommentsBefore.getCommentsBefore(member, sourceCode),
options.matcher,
)) ||
(options.partitionByNewLine &&
lastSortingNode &&
getLinesBetween.getLinesBetween(
sourceCode,
lastSortingNode,
sortingNode,
))
) {
accumulator.push([])
}
;(_e = accumulator.at(-1)) == null ? void 0 : _e.push(sortingNode)
return accumulator
},
[[]],
)
for (let nodes of formattedMembers) {
let groupedByKind
if (options.groupKind !== 'mixed') {
groupedByKind = nodes.reduce(
(accumulator, currentNode) => {
let requiredIndex =
options.groupKind === 'required-first' ? 0 : 1
let optionalIndex =
options.groupKind === 'required-first' ? 1 : 0
if (
'optional' in currentNode.node &&
currentNode.node.optional
) {
accumulator[optionalIndex].push(currentNode)
} else {
accumulator[requiredIndex].push(currentNode)
}
return accumulator
},
[[], []],
)
} else {
groupedByKind = [nodes]
}
let sortedNodes = []
for (let nodesByKind of groupedByKind) {
sortedNodes.push(
...sortNodesByGroups.sortNodesByGroups(nodesByKind, options),
)
}
pairwise.pairwise(nodes, (left, right) => {
let indexOfLeft = sortedNodes.indexOf(left)
let indexOfRight = sortedNodes.indexOf(right)
if (indexOfLeft > indexOfRight) {
let leftNum = getGroupNumber.getGroupNumber(options.groups, left)
let rightNum = getGroupNumber.getGroupNumber(
options.groups,
right,
)
context.report({
messageId:
leftNum !== rightNum
? 'unexpectedObjectTypesGroupOrder'
: 'unexpectedObjectTypesOrder',
data: {
left: toSingleLine.toSingleLine(left.name),
leftGroup: left.group,
right: toSingleLine.toSingleLine(right.name),
rightGroup: right.group,
},
node: right.node,
fix: fixer =>
makeFixes.makeFixes(
fixer,
nodes,
sortedNodes,
sourceCode,
options,
),
})
}
})
}
}
},
}),
defaultOptions: [defaultOptions],
name: 'sort-object-types',
})
module.exports = sortObjectTypes
'use strict'
const commonJsonSchemas = require('../utils/common-json-schemas.js')
const sortNodesByDependencies = require('../utils/sort-nodes-by-dependencies.js')
const validateNewlinesAndPartitionConfiguration = require('../utils/validate-newlines-and-partition-configuration.js')
const validateGroupsConfiguration = require('../utils/validate-groups-configuration.js')
const getEslintDisabledLines = require('../utils/get-eslint-disabled-lines.js')
const isNodeEslintDisabled = require('../utils/is-node-eslint-disabled.js')
const isPartitionComment = require('../utils/is-partition-comment.js')
const sortNodesByGroups = require('../utils/sort-nodes-by-groups.js')
const getCommentsBefore = require('../utils/get-comments-before.js')
const makeNewlinesFixes = require('../utils/make-newlines-fixes.js')
const getNewlinesErrors = require('../utils/get-newlines-errors.js')
const createEslintRule = require('../utils/create-eslint-rule.js')

@@ -12,5 +18,5 @@ const getLinesBetween = require('../utils/get-lines-between.js')

const getNodeParent = require('../utils/get-node-parent.js')
const toSingleLine = require('../utils/to-single-line.js')
const rangeToDiff = require('../utils/range-to-diff.js')
const getSettings = require('../utils/get-settings.js')
const isSortable = require('../utils/is-sortable.js')
const useGroups = require('../utils/use-groups.js')

@@ -21,164 +27,34 @@ const makeFixes = require('../utils/make-fixes.js')

const matches = require('../utils/matches.js')
let defaultOptions = {
partitionByNewLine: false,
partitionByComment: false,
newlinesBetween: 'ignore',
specialCharacters: 'keep',
styledComponents: true,
destructureOnly: false,
type: 'alphabetical',
ignorePattern: [],
ignoreCase: true,
customGroups: {},
locales: 'en-US',
order: 'asc',
groups: [],
}
const sortObjects = createEslintRule.createEslintRule({
name: 'sort-objects',
meta: {
type: 'suggestion',
docs: {
description: 'Enforce sorted objects.',
},
fixable: 'code',
schema: [
{
type: 'object',
properties: {
type: {
description: 'Specifies the sorting method.',
type: 'string',
enum: ['alphabetical', 'natural', 'line-length'],
},
order: {
description:
'Determines whether the sorted items should be in ascending or descending order.',
type: 'string',
enum: ['asc', 'desc'],
},
matcher: {
description: 'Specifies the string matcher.',
type: 'string',
enum: ['minimatch', 'regex'],
},
ignoreCase: {
description:
'Controls whether sorting should be case-sensitive or not.',
type: 'boolean',
},
specialCharacters: {
description:
'Controls how special characters should be handled before sorting.',
type: 'string',
enum: ['remove', 'trim', 'keep'],
},
partitionByComment: {
description:
'Allows you to use comments to separate the keys of objects into logical groups.',
anyOf: [
{
type: 'array',
items: {
type: 'string',
},
},
{
type: 'boolean',
},
{
type: 'string',
},
],
},
partitionByNewLine: {
description:
'Allows to use spaces to separate the nodes into logical groups.',
type: 'boolean',
},
styledComponents: {
description: 'Controls whether to sort styled components.',
type: 'boolean',
},
destructureOnly: {
description: 'Controls whether to sort only destructured objects.',
type: 'boolean',
},
ignorePattern: {
description:
'Specifies names or patterns for nodes that should be ignored by rule.',
items: {
type: 'string',
},
type: 'array',
},
groups: {
description: 'Specifies the order of the groups.',
type: 'array',
items: {
oneOf: [
{
type: 'string',
},
{
type: 'array',
items: {
type: 'string',
},
},
],
},
},
customGroups: {
description: 'Specifies custom groups.',
type: 'object',
additionalProperties: {
oneOf: [
{
type: 'string',
},
{
type: 'array',
items: {
type: 'string',
},
},
],
},
},
},
additionalProperties: false,
},
],
messages: {
unexpectedObjectsGroupOrder:
'Expected "{{right}}" ({{rightGroup}}) to come before "{{left}}" ({{leftGroup}}).',
unexpectedObjectsOrder: 'Expected "{{right}}" to come before "{{left}}".',
unexpectedObjectsDependencyOrder:
'Expected dependency "{{right}}" to come before "{{nodeDependentOnRight}}".',
},
},
defaultOptions: [
{
type: 'alphabetical',
order: 'asc',
ignoreCase: true,
specialCharacters: 'keep',
matcher: 'minimatch',
partitionByComment: false,
partitionByNewLine: false,
styledComponents: true,
destructureOnly: false,
ignorePattern: [],
groups: [],
customGroups: {},
},
],
create: context => {
let sortObject = node => {
let settings = getSettings.getSettings(context.settings)
let options = complete.complete(context.options.at(0), settings, {
partitionByNewLine: false,
partitionByComment: false,
styledComponents: true,
destructureOnly: false,
type: 'alphabetical',
ignorePattern: [],
matcher: 'minimatch',
ignoreCase: true,
specialCharacters: 'keep',
customGroups: {},
order: 'asc',
groups: [],
})
let options = complete.complete(
context.options.at(0),
settings,
defaultOptions,
)
validateGroupsConfiguration.validateGroupsConfiguration(
options.groups,
['unknown'],
['multiline', 'method', 'unknown'],
Object.keys(options.customGroups),
)
validateNewlinesAndPartitionConfiguration.validateNewlinesAndPartitionConfiguration(
options,
)
let shouldIgnore = false

@@ -189,3 +65,3 @@ if (options.destructureOnly) {

if (!shouldIgnore && options.ignorePattern.length) {
let varParent = getNodeParent.getNodeParent(node, [
let variableParent = getNodeParent.getNodeParent(node, [
'VariableDeclarator',

@@ -195,8 +71,9 @@ 'Property',

let parentId =
(varParent == null ? void 0 : varParent.type) === 'VariableDeclarator'
? varParent.id
: varParent == null
(variableParent == null ? void 0 : variableParent.type) ===
'VariableDeclarator'
? variableParent.id
: variableParent == null
? void 0
: varParent.key
let varIdentifier =
: variableParent.key
let variableIdentifier =
(parentId == null ? void 0 : parentId.type) === 'Identifier'

@@ -207,5 +84,8 @@ ? parentId.name

options.ignorePattern.some(pattern =>
matches.matches(identifier, pattern, options.matcher),
matches.matches(identifier, pattern),
)
if (typeof varIdentifier === 'string' && checkMatch(varIdentifier)) {
if (
typeof variableIdentifier === 'string' &&
checkMatch(variableIdentifier)
) {
shouldIgnore = true

@@ -223,211 +103,268 @@ }

}
if (!shouldIgnore && node.properties.length > 1) {
let isStyledCallExpression = identifier =>
identifier.type === 'Identifier' && identifier.name === 'styled'
let isCssCallExpression = identifier =>
identifier.type === 'Identifier' && identifier.name === 'css'
let isStyledComponents = styledNode =>
styledNode !== void 0 &&
((styledNode.type === 'CallExpression' &&
(isCssCallExpression(styledNode.callee) ||
(styledNode.callee.type === 'MemberExpression' &&
isStyledCallExpression(styledNode.callee.object)) ||
(styledNode.callee.type === 'CallExpression' &&
isStyledCallExpression(styledNode.callee.callee)))) ||
(styledNode.type === 'JSXExpressionContainer' &&
styledNode.parent.type === 'JSXAttribute' &&
styledNode.parent.name.name === 'style'))
if (
!options.styledComponents &&
(isStyledComponents(node.parent) ||
(node.parent.type === 'ArrowFunctionExpression' &&
isStyledComponents(node.parent.parent)))
) {
return
}
let sourceCode = getSourceCode.getSourceCode(context)
let extractDependencies = init => {
let dependencies = []
let checkNode = nodeValue => {
if (
nodeValue.type === 'ArrowFunctionExpression' ||
nodeValue.type === 'FunctionExpression'
) {
return
if (shouldIgnore || !isSortable.isSortable(node.properties)) {
return
}
let isStyledCallExpression = identifier =>
identifier.type === 'Identifier' && identifier.name === 'styled'
let isCssCallExpression = identifier =>
identifier.type === 'Identifier' && identifier.name === 'css'
let isStyledComponents = styledNode =>
!!styledNode &&
((styledNode.type === 'CallExpression' &&
(isCssCallExpression(styledNode.callee) ||
(styledNode.callee.type === 'MemberExpression' &&
isStyledCallExpression(styledNode.callee.object)) ||
(styledNode.callee.type === 'CallExpression' &&
isStyledCallExpression(styledNode.callee.callee)))) ||
(styledNode.type === 'JSXExpressionContainer' &&
styledNode.parent.type === 'JSXAttribute' &&
styledNode.parent.name.name === 'style'))
if (
!options.styledComponents &&
(isStyledComponents(node.parent) ||
(node.parent.type === 'ArrowFunctionExpression' &&
isStyledComponents(node.parent.parent)))
) {
return
}
let sourceCode = getSourceCode.getSourceCode(context)
let eslintDisabledLines = getEslintDisabledLines.getEslintDisabledLines({
ruleName: context.id,
sourceCode,
})
let extractDependencies = init => {
let dependencies = []
let checkNode = nodeValue => {
if (
nodeValue.type === 'ArrowFunctionExpression' ||
nodeValue.type === 'FunctionExpression'
) {
return
}
if (nodeValue.type === 'Identifier') {
dependencies.push(nodeValue.name)
}
if (nodeValue.type === 'Property') {
traverseNode(nodeValue.key)
traverseNode(nodeValue.value)
}
if (nodeValue.type === 'ConditionalExpression') {
traverseNode(nodeValue.test)
traverseNode(nodeValue.consequent)
traverseNode(nodeValue.alternate)
}
if (
'expression' in nodeValue &&
typeof nodeValue.expression !== 'boolean'
) {
traverseNode(nodeValue.expression)
}
if ('object' in nodeValue) {
traverseNode(nodeValue.object)
}
if ('callee' in nodeValue) {
traverseNode(nodeValue.callee)
}
if ('left' in nodeValue) {
traverseNode(nodeValue.left)
}
if ('right' in nodeValue) {
traverseNode(nodeValue.right)
}
if ('elements' in nodeValue) {
let elements = nodeValue.elements.filter(
currentNode => currentNode !== null,
)
for (let element of elements) {
traverseNode(element)
}
if (nodeValue.type === 'Identifier') {
dependencies.push(nodeValue.name)
}
if ('argument' in nodeValue && nodeValue.argument) {
traverseNode(nodeValue.argument)
}
if ('arguments' in nodeValue) {
for (let argument of nodeValue.arguments) {
traverseNode(argument)
}
if (nodeValue.type === 'Property') {
traverseNode(nodeValue.key)
traverseNode(nodeValue.value)
}
if ('properties' in nodeValue) {
for (let property of nodeValue.properties) {
traverseNode(property)
}
if (nodeValue.type === 'ConditionalExpression') {
traverseNode(nodeValue.test)
traverseNode(nodeValue.consequent)
traverseNode(nodeValue.alternate)
}
if ('expressions' in nodeValue) {
for (let nodeExpression of nodeValue.expressions) {
traverseNode(nodeExpression)
}
}
}
let traverseNode = nodeValue => {
checkNode(nodeValue)
}
traverseNode(init)
return dependencies
}
let formatProperties = props =>
props.reduce(
(accumulator, property) => {
var _a
if (
'expression' in nodeValue &&
typeof nodeValue.expression !== 'boolean'
property.type === 'SpreadElement' ||
property.type === 'RestElement'
) {
traverseNode(nodeValue.expression)
accumulator.push([])
return accumulator
}
if ('object' in nodeValue) {
traverseNode(nodeValue.object)
let comments = getCommentsBefore.getCommentsBefore(
property,
sourceCode,
)
let lastProperty =
(_a = accumulator.at(-1)) == null ? void 0 : _a.at(-1)
let name
let dependencies = []
let { setCustomGroups, defineGroup, getGroup } =
useGroups.useGroups(options)
if (property.key.type === 'Identifier') {
;({ name } = property.key)
} else if (property.key.type === 'Literal') {
name = `${property.key.value}`
} else {
name = sourceCode.getText(property.key)
}
if ('callee' in nodeValue) {
traverseNode(nodeValue.callee)
if (property.value.type === 'AssignmentPattern') {
dependencies = extractDependencies(property.value)
}
if ('left' in nodeValue) {
traverseNode(nodeValue.left)
setCustomGroups(options.customGroups, name)
if (
property.value.type === 'ArrowFunctionExpression' ||
property.value.type === 'FunctionExpression'
) {
defineGroup('method')
}
if ('right' in nodeValue) {
traverseNode(nodeValue.right)
if (property.loc.start.line !== property.loc.end.line) {
defineGroup('multiline')
}
if ('elements' in nodeValue) {
nodeValue.elements
.filter(currentNode => currentNode !== null)
.forEach(traverseNode)
let propertySortingNode = {
isEslintDisabled: isNodeEslintDisabled.isNodeEslintDisabled(
property,
eslintDisabledLines,
),
size: rangeToDiff.rangeToDiff(property, sourceCode),
group: getGroup(),
node: property,
dependencies,
name,
}
if ('argument' in nodeValue && nodeValue.argument) {
traverseNode(nodeValue.argument)
if (
(options.partitionByNewLine &&
lastProperty &&
getLinesBetween.getLinesBetween(
sourceCode,
lastProperty,
propertySortingNode,
)) ||
(options.partitionByComment &&
isPartitionComment.hasPartitionComment(
options.partitionByComment,
comments,
))
) {
accumulator.push([])
}
if ('arguments' in nodeValue) {
nodeValue.arguments.forEach(traverseNode)
}
if ('properties' in nodeValue) {
nodeValue.properties.forEach(traverseNode)
}
if ('expressions' in nodeValue) {
nodeValue.expressions.forEach(traverseNode)
}
accumulator.at(-1).push(propertySortingNode)
return accumulator
},
[[]],
)
let formattedMembers = formatProperties(node.properties)
let sortNodesIgnoringEslintDisabledNodes = ignoreEslintDisabledNodes =>
sortNodesByDependencies.sortNodesByDependencies(
formattedMembers.flatMap(nodes2 =>
sortNodesByGroups.sortNodesByGroups(nodes2, options, {
ignoreEslintDisabledNodes,
}),
),
{
ignoreEslintDisabledNodes,
},
)
let sortedNodes = sortNodesIgnoringEslintDisabledNodes(false)
let sortedNodesExcludingEslintDisabled =
sortNodesIgnoringEslintDisabledNodes(true)
let nodes = formattedMembers.flat()
pairwise.pairwise(nodes, (left, right) => {
let leftNumber = getGroupNumber.getGroupNumber(options.groups, left)
let rightNumber = getGroupNumber.getGroupNumber(options.groups, right)
let indexOfLeft = sortedNodes.indexOf(left)
let indexOfRight = sortedNodes.indexOf(right)
let indexOfRightExcludingEslintDisabled =
sortedNodesExcludingEslintDisabled.indexOf(right)
let messageIds = []
let firstUnorderedNodeDependentOnRight
if (
indexOfLeft > indexOfRight ||
indexOfLeft >= indexOfRightExcludingEslintDisabled
) {
firstUnorderedNodeDependentOnRight =
sortNodesByDependencies.getFirstUnorderedNodeDependentOn(
right,
nodes,
)
if (firstUnorderedNodeDependentOnRight) {
messageIds.push('unexpectedObjectsDependencyOrder')
} else {
messageIds.push(
leftNumber === rightNumber
? 'unexpectedObjectsOrder'
: 'unexpectedObjectsGroupOrder',
)
}
let traverseNode = nodeValue => {
checkNode(nodeValue)
}
traverseNode(init)
return dependencies
}
let formatProperties = props =>
props.reduce(
(accumulator, prop) => {
var _a
if (
prop.type === 'SpreadElement' ||
prop.type === 'RestElement'
) {
accumulator.push([])
return accumulator
}
let comments = getCommentsBefore.getCommentsBefore(
prop,
messageIds = [
...messageIds,
...getNewlinesErrors.getNewlinesErrors({
missedSpacingError: 'missedSpacingBetweenObjectMembers',
extraSpacingError: 'extraSpacingBetweenObjectMembers',
rightNum: rightNumber,
leftNum: leftNumber,
sourceCode,
options,
right,
left,
}),
]
for (let messageId of messageIds) {
context.report({
fix: fixer => [
...makeFixes.makeFixes({
sortedNodes: sortedNodesExcludingEslintDisabled,
sourceCode,
)
let lastProp =
(_a = accumulator.at(-1)) == null ? void 0 : _a.at(-1)
if (
options.partitionByComment &&
isPartitionComment.hasPartitionComment(
options.partitionByComment,
comments,
options.matcher,
)
) {
accumulator.push([])
}
let name
let position = 'ignore'
let dependencies = []
let { getGroup, setCustomGroups } = useGroups.useGroups(options)
if (prop.key.type === 'Identifier') {
;({ name } = prop.key)
} else if (prop.key.type === 'Literal') {
name = `${prop.key.value}`
} else {
name = sourceCode.text.slice(...prop.key.range)
}
let propSortingNode = {
size: rangeToDiff.rangeToDiff(prop.range),
node: prop,
name,
}
if (
options.partitionByNewLine &&
lastProp &&
getLinesBetween.getLinesBetween(
sourceCode,
lastProp,
propSortingNode,
)
) {
accumulator.push([])
}
if (prop.value.type === 'AssignmentPattern') {
dependencies = extractDependencies(prop.value)
}
setCustomGroups(options.customGroups, name)
let value = {
...propSortingNode,
group: getGroup(),
dependencies,
position,
}
accumulator.at(-1).push(value)
return accumulator
options,
fixer,
nodes,
}),
...makeNewlinesFixes.makeNewlinesFixes({
sortedNodes: sortedNodesExcludingEslintDisabled,
sourceCode,
options,
fixer,
nodes,
}),
],
data: {
nodeDependentOnRight:
firstUnorderedNodeDependentOnRight == null
? void 0
: firstUnorderedNodeDependentOnRight.name,
rightGroup: right.group,
leftGroup: left.group,
right: right.name,
left: left.name,
},
[[]],
)
let formattedMembers = formatProperties(node.properties)
let sortedNodes = sortNodesByDependencies.sortNodesByDependencies(
formattedMembers
.map(nodes2 => sortNodesByGroups.sortNodesByGroups(nodes2, options))
.flat(),
)
let nodes = formattedMembers.flat()
pairwise.pairwise(nodes, (left, right) => {
let indexOfLeft = sortedNodes.indexOf(left)
let indexOfRight = sortedNodes.indexOf(right)
if (indexOfLeft > indexOfRight) {
let firstUnorderedNodeDependentOnRight =
sortNodesByDependencies.getFirstUnorderedNodeDependentOn(
right,
nodes,
)
let leftNum = getGroupNumber.getGroupNumber(options.groups, left)
let rightNum = getGroupNumber.getGroupNumber(options.groups, right)
let messageId
if (firstUnorderedNodeDependentOnRight) {
messageId = 'unexpectedObjectsDependencyOrder'
} else {
messageId =
leftNum !== rightNum
? 'unexpectedObjectsGroupOrder'
: 'unexpectedObjectsOrder'
}
context.report({
messageId,
data: {
left: toSingleLine.toSingleLine(left.name),
leftGroup: left.group,
right: toSingleLine.toSingleLine(right.name),
rightGroup: right.group,
nodeDependentOnRight:
firstUnorderedNodeDependentOnRight == null
? void 0
: firstUnorderedNodeDependentOnRight.name,
},
node: right.node,
fix: fixer =>
makeFixes.makeFixes(
fixer,
nodes,
sortedNodes,
sourceCode,
options,
),
})
}
})
}
node: right.node,
messageId,
})
}
})
}

@@ -439,3 +376,63 @@ return {

},
meta: {
schema: [
{
properties: {
ignorePattern: {
description:
'Specifies names or patterns for nodes that should be ignored by rule.',
items: {
type: 'string',
},
type: 'array',
},
partitionByComment: {
...commonJsonSchemas.partitionByCommentJsonSchema,
description:
'Allows you to use comments to separate the keys of objects into logical groups.',
},
destructureOnly: {
description: 'Controls whether to sort only destructured objects.',
type: 'boolean',
},
styledComponents: {
description: 'Controls whether to sort styled components.',
type: 'boolean',
},
partitionByNewLine: commonJsonSchemas.partitionByNewLineJsonSchema,
specialCharacters: commonJsonSchemas.specialCharactersJsonSchema,
newlinesBetween: commonJsonSchemas.newlinesBetweenJsonSchema,
customGroups: commonJsonSchemas.customGroupsJsonSchema,
ignoreCase: commonJsonSchemas.ignoreCaseJsonSchema,
locales: commonJsonSchemas.localesJsonSchema,
groups: commonJsonSchemas.groupsJsonSchema,
order: commonJsonSchemas.orderJsonSchema,
type: commonJsonSchemas.typeJsonSchema,
},
additionalProperties: false,
type: 'object',
},
],
messages: {
unexpectedObjectsGroupOrder:
'Expected "{{right}}" ({{rightGroup}}) to come before "{{left}}" ({{leftGroup}}).',
unexpectedObjectsDependencyOrder:
'Expected dependency "{{right}}" to come before "{{nodeDependentOnRight}}".',
missedSpacingBetweenObjectMembers:
'Missed spacing between "{{left}}" and "{{right}}" objects.',
extraSpacingBetweenObjectMembers:
'Extra spacing between "{{left}}" and "{{right}}" objects.',
unexpectedObjectsOrder: 'Expected "{{right}}" to come before "{{left}}".',
},
docs: {
url: 'https://perfectionist.dev/rules/sort-objects',
description: 'Enforce sorted objects.',
recommended: true,
},
type: 'suggestion',
fixable: 'code',
},
defaultOptions: [defaultOptions],
name: 'sort-objects',
})
module.exports = sortObjects
'use strict'
const sortArrayIncludes = require('./sort-array-includes.js')
const createEslintRule = require('../utils/create-eslint-rule.js')
const sortArrayIncludes = require('./sort-array-includes.js')
const sortSets = createEslintRule.createEslintRule({
name: 'sort-sets',
meta: {
type: 'suggestion',
docs: {
description: 'Enforce sorted sets.',
},
fixable: 'code',
schema: [sortArrayIncludes.jsonSchema],
messages: {
unexpectedSetsOrder: 'Expected "{{right}}" to come before "{{left}}".',
},
},
defaultOptions: [
{
type: 'alphabetical',
order: 'asc',
ignoreCase: true,
specialCharacters: 'keep',
matcher: 'minimatch',
groupKind: 'literals-first',
},
],
create: context => ({

@@ -49,3 +27,18 @@ NewExpression: node => {

}),
meta: {
docs: {
url: 'https://perfectionist.dev/rules/sort-sets',
description: 'Enforce sorted sets.',
recommended: true,
},
messages: {
unexpectedSetsOrder: 'Expected "{{right}}" to come before "{{left}}".',
},
schema: [sortArrayIncludes.jsonSchema],
type: 'suggestion',
fixable: 'code',
},
defaultOptions: [sortArrayIncludes.defaultOptions],
name: 'sort-sets',
})
module.exports = sortSets
'use strict'
const commonJsonSchemas = require('../utils/common-json-schemas.js')
const makeCommentAfterFixes = require('../utils/make-comment-after-fixes.js')
const createEslintRule = require('../utils/create-eslint-rule.js')

@@ -6,3 +8,3 @@ const getSourceCode = require('../utils/get-source-code.js')

const getSettings = require('../utils/get-settings.js')
const isPositive = require('../utils/is-positive.js')
const isSortable = require('../utils/is-sortable.js')
const makeFixes = require('../utils/make-fixes.js')

@@ -13,253 +15,269 @@ const sortNodes = require('../utils/sort-nodes.js')

const compare = require('../utils/compare.js')
let defaultOptions = {
specialCharacters: 'keep',
type: 'alphabetical',
ignoreCase: true,
locales: 'en-US',
order: 'asc',
}
const sortSwitchCase = createEslintRule.createEslintRule({
name: 'sort-switch-case',
meta: {
type: 'suggestion',
docs: {
description: 'Enforce sorted switch cases.',
},
fixable: 'code',
schema: [
{
type: 'object',
properties: {
type: {
description: 'Specifies the sorting method.',
type: 'string',
enum: ['alphabetical', 'natural', 'line-length'],
},
order: {
description:
'Determines whether the sorted items should be in ascending or descending order.',
type: 'string',
enum: ['asc', 'desc'],
},
ignoreCase: {
description:
'Controls whether sorting should be case-sensitive or not.',
type: 'boolean',
},
specialCharacters: {
description:
'Controls how special characters should be handled before sorting.',
type: 'string',
enum: ['remove', 'trim', 'keep'],
},
},
additionalProperties: false,
},
],
messages: {
unexpectedSwitchCaseOrder:
'Expected "{{right}}" to come before "{{left}}".',
},
},
defaultOptions: [
{
type: 'alphabetical',
order: 'asc',
ignoreCase: true,
specialCharacters: 'keep',
},
],
create: context => ({
SwitchStatement: node => {
SwitchStatement: switchNode => {
if (!isSortable.isSortable(switchNode.cases)) {
return
}
let settings = getSettings.getSettings(context.settings)
let options = complete.complete(context.options.at(0), settings, {
type: 'alphabetical',
ignoreCase: true,
specialCharacters: 'keep',
order: 'asc',
})
let options = complete.complete(
context.options.at(0),
settings,
defaultOptions,
)
let sourceCode = getSourceCode.getSourceCode(context)
let isDiscriminantTrue =
node.discriminant.type === 'Literal' && node.discriminant.value === true
let isCasesHasBreak = node.cases
.filter(caseNode => caseNode.test !== null)
.every(
caseNode =>
caseNode.consequent.length === 0 ||
caseNode.consequent.some(
currentConsequent =>
currentConsequent.type === 'BreakStatement' ||
currentConsequent.type === 'ReturnStatement' ||
currentConsequent.type === 'BlockStatement',
),
switchNode.discriminant.type === 'Literal' &&
switchNode.discriminant.value === true
if (isDiscriminantTrue) {
return
}
let caseNameSortingNodeGroups = switchNode.cases.reduce(
(accumulator, caseNode, index) => {
if (caseNode.test) {
accumulator.at(-1).push({
size: rangeToDiff.rangeToDiff(caseNode.test, sourceCode),
name: getCaseName(sourceCode, caseNode),
isEslintDisabled: false,
node: caseNode.test,
})
}
if (
caseNode.consequent.length &&
index !== switchNode.cases.length - 1
) {
accumulator.push([])
}
return accumulator
},
[[]],
)
let hasUnsortedNodes = false
for (let caseNodesSortingNodeGroup of caseNameSortingNodeGroups) {
let sortedCaseNameSortingNodes = sortNodes.sortNodes(
caseNodesSortingNodeGroup,
options,
)
if (!isDiscriminantTrue && isCasesHasBreak) {
let nodes = node.cases.map(caseNode => {
var _a, _b
let name
let isDefaultClause = false
if (((_a = caseNode.test) == null ? void 0 : _a.type) === 'Literal') {
name = `${caseNode.test.value}`
} else if (caseNode.test === null) {
name = 'default'
isDefaultClause = true
} else {
name = sourceCode.text.slice(...caseNode.test.range)
hasUnsortedNodes ||
(hasUnsortedNodes = sortedCaseNameSortingNodes.some(
(node, index) => node !== caseNodesSortingNodeGroup[index],
))
pairwise.pairwise(caseNodesSortingNodeGroup, (left, right) => {
let indexOfLeft = sortedCaseNameSortingNodes.indexOf(left)
let indexOfRight = sortedCaseNameSortingNodes.indexOf(right)
if (indexOfLeft < indexOfRight) {
return
}
return {
size: rangeToDiff.rangeToDiff(
((_b = caseNode.test) == null ? void 0 : _b.range) ??
caseNode.range,
),
node: caseNode,
isDefaultClause,
name,
}
context.report({
fix: fixer =>
makeFixes.makeFixes({
sortedNodes: sortedCaseNameSortingNodes,
nodes: caseNodesSortingNodeGroup,
sourceCode,
fixer,
}),
data: {
right: right.name,
left: left.name,
},
messageId: 'unexpectedSwitchCaseOrder',
node: right.node,
})
})
pairwise.pairwise(nodes, (left, right, iteration) => {
let compareValue
let lefter = nodes.at(iteration - 1)
let isCaseGrouped =
(lefter == null ? void 0 : lefter.node.consequent.length) === 0 &&
left.node.consequent.length !== 0
let isGroupContainsDefault = group =>
group.some(currentNode => currentNode.isDefaultClause)
let leftCaseGroup = [left]
let rightCaseGroup = [right]
for (let i = iteration - 1; i >= 0; i--) {
if (nodes.at(i).node.consequent.length === 0) {
leftCaseGroup.unshift(nodes.at(i))
} else {
break
}
let sortingNodes = switchNode.cases.map(caseNode => ({
size: caseNode.test
? rangeToDiff.rangeToDiff(caseNode.test, sourceCode)
: 'default'.length,
name: getCaseName(sourceCode, caseNode),
addSafetySemicolonWhenInline: true,
isDefaultClause: !caseNode.test,
isEslintDisabled: false,
node: caseNode,
}))
let sortingNodeGroupsForDefaultSort = reduceCaseSortingNodes(
sortingNodes,
caseNode => !!caseNode.node.consequent.length,
)
let sortingNodesGroupWithDefault = sortingNodeGroupsForDefaultSort.find(
caseNodeGroup => caseNodeGroup.some(node => node.isDefaultClause),
)
if (
sortingNodesGroupWithDefault &&
!sortingNodesGroupWithDefault.at(-1).isDefaultClause
) {
let defaultCase = sortingNodesGroupWithDefault.find(
node => node.isDefaultClause,
)
let lastCase = sortingNodesGroupWithDefault.at(-1)
context.report({
fix: fixer => {
let punctuatorAfterLastCase = sourceCode.getTokenAfter(
lastCase.node.test,
)
let lastCaseRange = [
lastCase.node.range[0],
punctuatorAfterLastCase.range[1],
]
return [
fixer.replaceText(
defaultCase.node,
sourceCode.text.slice(...lastCaseRange),
),
fixer.replaceTextRange(
lastCaseRange,
sourceCode.getText(defaultCase.node),
),
...makeCommentAfterFixes.makeCommentAfterFixes({
sortedNode: punctuatorAfterLastCase,
node: defaultCase.node,
sourceCode,
fixer,
}),
...makeCommentAfterFixes.makeCommentAfterFixes({
node: punctuatorAfterLastCase,
sortedNode: defaultCase.node,
sourceCode,
fixer,
}),
]
},
data: {
left: defaultCase.name,
right: lastCase.name,
},
messageId: 'unexpectedSwitchCaseOrder',
node: defaultCase.node,
})
}
let sortingNodeGroupsForBlockSort = reduceCaseSortingNodes(
sortingNodes,
caseNode => caseHasBreakOrReturn(caseNode.node),
)
let lastNodeGroup = sortingNodeGroupsForBlockSort.at(-1)
let lastBlockCaseShouldStayInPlace = !caseHasBreakOrReturn(
lastNodeGroup.at(-1).node,
)
let sortedSortingNodeGroupsForBlockSort = [
...sortingNodeGroupsForBlockSort,
]
.sort((a, b) => {
if (lastBlockCaseShouldStayInPlace) {
if (a === lastNodeGroup) {
return 1
}
}
if (right.node.consequent.length === 0) {
for (let i = iteration + 1; i < nodes.length; i++) {
if (nodes.at(i).node.consequent.length === 0) {
rightCaseGroup.push(nodes.at(i))
} else {
rightCaseGroup.push(nodes.at(i))
break
}
if (b === lastNodeGroup) {
return -1
}
}
if (isGroupContainsDefault(leftCaseGroup)) {
compareValue = true
} else if (isGroupContainsDefault(rightCaseGroup)) {
compareValue = false
} else if (isCaseGrouped) {
compareValue = isPositive.isPositive(
compare.compare(leftCaseGroup[0], right, options),
)
} else {
compareValue = isPositive.isPositive(
compare.compare(left, right, options),
)
if (a.some(node => node.isDefaultClause)) {
return 1
}
if (compareValue) {
context.report({
messageId: 'unexpectedSwitchCaseOrder',
data: {
left: left.name,
right: right.name,
},
node: right.node,
fix: fixer => {
let additionalFixes = []
let nodeGroups = nodes.reduce(
(accumulator, currentNode, index) => {
var _a
if (index === 0) {
accumulator.at(-1).push(currentNode)
} else if (
((_a = accumulator.at(-1).at(-1)) == null
? void 0
: _a.node.consequent.length) === 0
) {
accumulator.at(-1).push(currentNode)
} else {
accumulator.push([currentNode])
}
return accumulator
},
[[]],
)
let sortedNodeGroups = nodeGroups
.map(group => {
var _a, _b
let sortedGroup = sortNodes
.sortNodes(group, options)
.sort((a, b) => {
if (b.isDefaultClause) {
return -1
}
return 1
})
if (group.at(-1).name !== sortedGroup.at(-1).name) {
let consequentNodeIndex = sortedGroup.findIndex(
currentNode => currentNode.node.consequent.length !== 0,
)
let firstSortedNodeConsequent =
sortedGroup.at(consequentNodeIndex).node.consequent
let consequentStart =
(_a = firstSortedNodeConsequent.at(0)) == null
? void 0
: _a.range.at(0)
let consequentEnd =
(_b = firstSortedNodeConsequent.at(-1)) == null
? void 0
: _b.range.at(1)
let lastNode = group.at(-1).node
if (consequentStart && consequentEnd && lastNode.test) {
lastNode.range = [
lastNode.range.at(0),
lastNode.test.range.at(1) + 1,
]
additionalFixes.push(
...makeFixes.makeFixes(
fixer,
group,
sortedGroup,
sourceCode,
),
fixer.removeRange([
lastNode.range.at(1),
consequentEnd,
]),
fixer.insertTextAfter(
lastNode,
sourceCode.text.slice(
lastNode.range.at(1),
consequentEnd,
),
),
)
}
}
return sortedGroup
})
.sort((a, b) => {
if (isGroupContainsDefault(a)) {
return 1
} else if (isGroupContainsDefault(b)) {
return -1
}
return compare.compare(a.at(0), b.at(0), options)
})
let sortedNodes = sortedNodeGroups.flat()
for (let max = sortedNodes.length, i = 0; i < max; i++) {
if (sortedNodes.at(i).isDefaultClause) {
sortedNodes.push(sortedNodes.splice(i, 1).at(0))
}
}
if (additionalFixes.length) {
return additionalFixes
}
return makeFixes.makeFixes(
fixer,
nodes,
sortedNodes,
sourceCode,
)
},
})
if (b.some(node => node.isDefaultClause)) {
return -1
}
return compare.compare(a.at(0), b.at(0), options)
})
}
.flat()
let sortingNodeGroupsForBlockSortFlat =
sortingNodeGroupsForBlockSort.flat()
pairwise.pairwise(sortingNodeGroupsForBlockSortFlat, (left, right) => {
let indexOfLeft = sortedSortingNodeGroupsForBlockSort.indexOf(left)
let indexOfRight = sortedSortingNodeGroupsForBlockSort.indexOf(right)
if (indexOfLeft < indexOfRight) {
return
}
context.report({
fix: fixer =>
hasUnsortedNodes
? []
: makeFixes.makeFixes({
sortedNodes: sortedSortingNodeGroupsForBlockSort,
nodes: sortingNodeGroupsForBlockSortFlat,
sourceCode,
fixer,
}),
data: {
right: right.name,
left: left.name,
},
messageId: 'unexpectedSwitchCaseOrder',
node: right.node,
})
})
},
}),
meta: {
schema: [
{
properties: {
specialCharacters: commonJsonSchemas.specialCharactersJsonSchema,
ignoreCase: commonJsonSchemas.ignoreCaseJsonSchema,
locales: commonJsonSchemas.localesJsonSchema,
order: commonJsonSchemas.orderJsonSchema,
type: commonJsonSchemas.typeJsonSchema,
},
additionalProperties: false,
type: 'object',
},
],
docs: {
url: 'https://perfectionist.dev/rules/sort-switch-case',
description: 'Enforce sorted switch cases.',
recommended: true,
},
messages: {
unexpectedSwitchCaseOrder:
'Expected "{{right}}" to come before "{{left}}".',
},
type: 'suggestion',
fixable: 'code',
},
defaultOptions: [defaultOptions],
name: 'sort-switch-case',
})
let getCaseName = (sourceCode, caseNode) => {
var _a
if (((_a = caseNode.test) == null ? void 0 : _a.type) === 'Literal') {
return `${caseNode.test.value}`
} else if (caseNode.test === null) {
return 'default'
}
return sourceCode.getText(caseNode.test)
}
let reduceCaseSortingNodes = (caseNodes, endsBlock) =>
caseNodes.reduce(
(accumulator, caseNode, index) => {
accumulator.at(-1).push(caseNode)
if (endsBlock(caseNode) && index !== caseNodes.length - 1) {
accumulator.push([])
}
return accumulator
},
[[]],
)
let caseHasBreakOrReturn = caseNode => {
var _a
if (caseNode.consequent.length === 0) {
return false
}
if (
((_a = caseNode.consequent[0]) == null ? void 0 : _a.type) ===
'BlockStatement'
) {
return caseNode.consequent[0].body.some(statementIsBreakOrReturn)
}
return caseNode.consequent.some(currentConsequent =>
statementIsBreakOrReturn(currentConsequent),
)
}
let statementIsBreakOrReturn = statement =>
statement.type === 'BreakStatement' || statement.type === 'ReturnStatement'
module.exports = sortSwitchCase
'use strict'
const commonJsonSchemas = require('../utils/common-json-schemas.js')
const validateNewlinesAndPartitionConfiguration = require('../utils/validate-newlines-and-partition-configuration.js')
const validateGroupsConfiguration = require('../utils/validate-groups-configuration.js')
const getEslintDisabledLines = require('../utils/get-eslint-disabled-lines.js')
const isNodeEslintDisabled = require('../utils/is-node-eslint-disabled.js')
const isPartitionComment = require('../utils/is-partition-comment.js')
const sortNodesByGroups = require('../utils/sort-nodes-by-groups.js')
const getCommentsBefore = require('../utils/get-comments-before.js')
const makeNewlinesFixes = require('../utils/make-newlines-fixes.js')
const getNewlinesErrors = require('../utils/get-newlines-errors.js')
const createEslintRule = require('../utils/create-eslint-rule.js')

@@ -17,117 +23,22 @@ const getLinesBetween = require('../utils/get-lines-between.js')

const pairwise = require('../utils/pairwise.js')
let defaultOptions = {
specialCharacters: 'keep',
newlinesBetween: 'ignore',
partitionByNewLine: false,
partitionByComment: false,
type: 'alphabetical',
ignoreCase: true,
locales: 'en-US',
order: 'asc',
groups: [],
}
const sortUnionTypes = createEslintRule.createEslintRule({
name: 'sort-union-types',
meta: {
type: 'suggestion',
docs: {
description: 'Enforce sorted union types.',
},
fixable: 'code',
schema: [
{
type: 'object',
properties: {
type: {
description: 'Specifies the sorting method.',
type: 'string',
enum: ['alphabetical', 'natural', 'line-length'],
},
order: {
description:
'Determines whether the sorted items should be in ascending or descending order.',
type: 'string',
enum: ['asc', 'desc'],
},
matcher: {
description: 'Specifies the string matcher.',
type: 'string',
enum: ['minimatch', 'regex'],
},
ignoreCase: {
description:
'Controls whether sorting should be case-sensitive or not.',
type: 'boolean',
},
specialCharacters: {
description:
'Controls how special characters should be handled before sorting.',
type: 'string',
enum: ['remove', 'trim', 'keep'],
},
groups: {
description: 'Specifies the order of the groups.',
type: 'array',
items: {
oneOf: [
{
type: 'string',
},
{
type: 'array',
items: {
type: 'string',
},
},
],
},
},
partitionByComment: {
description:
'Allows you to use comments to separate the union types into logical groups.',
anyOf: [
{
type: 'array',
items: {
type: 'string',
},
},
{
type: 'boolean',
},
{
type: 'string',
},
],
},
partitionByNewLine: {
description:
'Allows to use spaces to separate the nodes into logical groups.',
type: 'boolean',
},
},
additionalProperties: false,
},
],
messages: {
unexpectedUnionTypesGroupOrder:
'Expected "{{right}}" ({{rightGroup}}) to come before "{{left}}" ({{leftGroup}}).',
unexpectedUnionTypesOrder:
'Expected "{{right}}" to come before "{{left}}".',
},
},
defaultOptions: [
{
type: 'alphabetical',
order: 'asc',
ignoreCase: true,
specialCharacters: 'keep',
matcher: 'minimatch',
partitionByNewLine: false,
partitionByComment: false,
groups: [],
},
],
create: context => ({
TSUnionType: node => {
let settings = getSettings.getSettings(context.settings)
let options = complete.complete(context.options.at(0), settings, {
type: 'alphabetical',
ignoreCase: true,
specialCharacters: 'keep',
order: 'asc',
groups: [],
matcher: 'minimatch',
partitionByNewLine: false,
partitionByComment: false,
})
let options = complete.complete(
context.options.at(0),
settings,
defaultOptions,
)
validateGroupsConfiguration.validateGroupsConfiguration(

@@ -152,9 +63,34 @@ options.groups,

)
validateNewlinesAndPartitionConfiguration.validateNewlinesAndPartitionConfiguration(
options,
)
let sourceCode = getSourceCode.getSourceCode(context)
let partitionComment = options.partitionByComment
let eslintDisabledLines = getEslintDisabledLines.getEslintDisabledLines({
ruleName: context.id,
sourceCode,
})
let formattedMembers = node.types.reduce(
(accumulator, type) => {
var _a, _b
let { getGroup, defineGroup } = useGroups.useGroups(options)
let { defineGroup, getGroup } = useGroups.useGroups(options)
switch (type.type) {
case 'TSTemplateLiteralType':
case 'TSLiteralType':
defineGroup('literal')
break
case 'TSIndexedAccessType':
case 'TSTypeReference':
case 'TSQualifiedName':
case 'TSArrayType':
case 'TSInferType':
defineGroup('named')
break
case 'TSIntersectionType':
defineGroup('intersection')
break
case 'TSUndefinedKeyword':
case 'TSNullKeyword':
case 'TSVoidKeyword':
defineGroup('nullish')
break
case 'TSConditionalType':

@@ -167,12 +103,5 @@ defineGroup('conditional')

break
case 'TSImportType':
defineGroup('import')
break
case 'TSIntersectionType':
defineGroup('intersection')
break
case 'TSAnyKeyword':
case 'TSBooleanKeyword':
case 'TSUnknownKeyword':
case 'TSBigIntKeyword':
case 'TSBooleanKeyword':
case 'TSNeverKeyword':
case 'TSNumberKeyword':

@@ -182,25 +111,17 @@ case 'TSObjectKeyword':

case 'TSSymbolKeyword':
case 'TSNeverKeyword':
case 'TSAnyKeyword':
case 'TSThisType':
case 'TSUnknownKeyword':
case 'TSIntrinsicKeyword':
defineGroup('keyword')
break
case 'TSLiteralType':
case 'TSTemplateLiteralType':
defineGroup('literal')
case 'TSTypeOperator':
case 'TSTypeQuery':
defineGroup('operator')
break
case 'TSArrayType':
case 'TSIndexedAccessType':
case 'TSInferType':
case 'TSTypeReference':
case 'TSQualifiedName':
defineGroup('named')
break
case 'TSTypeLiteral':
case 'TSMappedType':
case 'TSTypeLiteral':
defineGroup('object')
break
case 'TSTypeQuery':
case 'TSTypeOperator':
defineGroup('operator')
case 'TSImportType':
defineGroup('import')
break

@@ -213,7 +134,2 @@ case 'TSTupleType':

break
case 'TSNullKeyword':
case 'TSUndefinedKeyword':
case 'TSVoidKeyword':
defineGroup('nullish')
break
}

@@ -223,4 +139,8 @@ let lastSortingNode =

let sortingNode = {
name: sourceCode.text.slice(...type.range),
size: rangeToDiff.rangeToDiff(type.range),
isEslintDisabled: isNodeEslintDisabled.isNodeEslintDisabled(
type,
eslintDisabledLines,
),
size: rangeToDiff.rangeToDiff(type, sourceCode),
name: sourceCode.getText(type),
group: getGroup(),

@@ -230,7 +150,6 @@ node: type,

if (
(partitionComment &&
(options.partitionByComment &&
isPartitionComment.hasPartitionComment(
partitionComment,
getCommentsBefore.getCommentsBefore(type, sourceCode),
options.matcher,
options.partitionByComment,
getCommentsBefore.getCommentsBefore(type, sourceCode, '|'),
)) ||

@@ -253,29 +172,66 @@ (options.partitionByNewLine &&

for (let nodes of formattedMembers) {
let sortedNodes = sortNodesByGroups.sortNodesByGroups(nodes, options)
let sortNodesExcludingEslintDisabled = ignoreEslintDisabledNodes =>
sortNodesByGroups.sortNodesByGroups(nodes, options, {
ignoreEslintDisabledNodes,
})
let sortedNodes = sortNodesExcludingEslintDisabled(false)
let sortedNodesExcludingEslintDisabled =
sortNodesExcludingEslintDisabled(true)
pairwise.pairwise(nodes, (left, right) => {
let leftNumber = getGroupNumber.getGroupNumber(options.groups, left)
let rightNumber = getGroupNumber.getGroupNumber(options.groups, right)
let indexOfLeft = sortedNodes.indexOf(left)
let indexOfRight = sortedNodes.indexOf(right)
if (indexOfLeft > indexOfRight) {
let leftNum = getGroupNumber.getGroupNumber(options.groups, left)
let rightNum = getGroupNumber.getGroupNumber(options.groups, right)
let indexOfRightExcludingEslintDisabled =
sortedNodesExcludingEslintDisabled.indexOf(right)
let messageIds = []
if (
indexOfLeft > indexOfRight ||
indexOfLeft >= indexOfRightExcludingEslintDisabled
) {
messageIds.push(
leftNumber === rightNumber
? 'unexpectedUnionTypesOrder'
: 'unexpectedUnionTypesGroupOrder',
)
}
messageIds = [
...messageIds,
...getNewlinesErrors.getNewlinesErrors({
missedSpacingError: 'missedSpacingBetweenUnionTypes',
extraSpacingError: 'extraSpacingBetweenUnionTypes',
rightNum: rightNumber,
leftNum: leftNumber,
sourceCode,
options,
right,
left,
}),
]
for (let messageId of messageIds) {
context.report({
messageId:
leftNum !== rightNum
? 'unexpectedUnionTypesGroupOrder'
: 'unexpectedUnionTypesOrder',
fix: fixer => [
...makeFixes.makeFixes({
sortedNodes: sortedNodesExcludingEslintDisabled,
sourceCode,
options,
fixer,
nodes,
}),
...makeNewlinesFixes.makeNewlinesFixes({
sortedNodes: sortedNodesExcludingEslintDisabled,
sourceCode,
options,
fixer,
nodes,
}),
],
data: {
right: toSingleLine.toSingleLine(right.name),
left: toSingleLine.toSingleLine(left.name),
rightGroup: right.group,
leftGroup: left.group,
right: toSingleLine.toSingleLine(right.name),
rightGroup: right.group,
},
node: right.node,
fix: fixer =>
makeFixes.makeFixes(
fixer,
nodes,
sortedNodes,
sourceCode,
options,
),
messageId,
})

@@ -287,3 +243,45 @@ }

}),
meta: {
schema: [
{
properties: {
partitionByComment: {
...commonJsonSchemas.partitionByCommentJsonSchema,
description:
'Allows you to use comments to separate the union types into logical groups.',
},
partitionByNewLine: commonJsonSchemas.partitionByNewLineJsonSchema,
specialCharacters: commonJsonSchemas.specialCharactersJsonSchema,
newlinesBetween: commonJsonSchemas.newlinesBetweenJsonSchema,
ignoreCase: commonJsonSchemas.ignoreCaseJsonSchema,
locales: commonJsonSchemas.localesJsonSchema,
groups: commonJsonSchemas.groupsJsonSchema,
order: commonJsonSchemas.orderJsonSchema,
type: commonJsonSchemas.typeJsonSchema,
},
additionalProperties: false,
type: 'object',
},
],
messages: {
unexpectedUnionTypesGroupOrder:
'Expected "{{right}}" ({{rightGroup}}) to come before "{{left}}" ({{leftGroup}}).',
missedSpacingBetweenUnionTypes:
'Missed spacing between "{{left}}" and "{{right}}" types.',
extraSpacingBetweenUnionTypes:
'Extra spacing between "{{left}}" and "{{right}}" types.',
unexpectedUnionTypesOrder:
'Expected "{{right}}" to come before "{{left}}".',
},
docs: {
url: 'https://perfectionist.dev/rules/sort-union-types',
description: 'Enforce sorted union types.',
recommended: true,
},
type: 'suggestion',
fixable: 'code',
},
defaultOptions: [defaultOptions],
name: 'sort-union-types',
})
module.exports = sortUnionTypes
'use strict'
const commonJsonSchemas = require('../utils/common-json-schemas.js')
const sortNodesByDependencies = require('../utils/sort-nodes-by-dependencies.js')
const getEslintDisabledLines = require('../utils/get-eslint-disabled-lines.js')
const isNodeEslintDisabled = require('../utils/is-node-eslint-disabled.js')
const isPartitionComment = require('../utils/is-partition-comment.js')

@@ -11,2 +14,3 @@ const getCommentsBefore = require('../utils/get-comments-before.js')

const getSettings = require('../utils/get-settings.js')
const isSortable = require('../utils/is-sortable.js')
const sortNodes = require('../utils/sort-nodes.js')

@@ -16,253 +20,239 @@ const makeFixes = require('../utils/make-fixes.js')

const pairwise = require('../utils/pairwise.js')
let defaultOptions = {
specialCharacters: 'keep',
partitionByNewLine: false,
partitionByComment: false,
type: 'alphabetical',
ignoreCase: true,
locales: 'en-US',
order: 'asc',
}
const sortVariableDeclarations = createEslintRule.createEslintRule({
name: 'sort-variable-declarations',
create: context => ({
VariableDeclaration: node => {
if (!isSortable.isSortable(node.declarations)) {
return
}
let settings = getSettings.getSettings(context.settings)
let options = complete.complete(
context.options.at(0),
settings,
defaultOptions,
)
let sourceCode = getSourceCode.getSourceCode(context)
let eslintDisabledLines = getEslintDisabledLines.getEslintDisabledLines({
ruleName: context.id,
sourceCode,
})
let extractDependencies = init => {
let dependencies = []
let checkNode = nodeValue => {
if (
nodeValue.type === 'ArrowFunctionExpression' ||
nodeValue.type === 'FunctionExpression'
) {
return
}
if (nodeValue.type === 'Identifier') {
dependencies.push(nodeValue.name)
}
if (nodeValue.type === 'Property') {
traverseNode(nodeValue.key)
traverseNode(nodeValue.value)
}
if (nodeValue.type === 'ConditionalExpression') {
traverseNode(nodeValue.test)
traverseNode(nodeValue.consequent)
traverseNode(nodeValue.alternate)
}
if (
'expression' in nodeValue &&
typeof nodeValue.expression !== 'boolean'
) {
traverseNode(nodeValue.expression)
}
if ('object' in nodeValue) {
traverseNode(nodeValue.object)
}
if ('callee' in nodeValue) {
traverseNode(nodeValue.callee)
}
if ('left' in nodeValue) {
traverseNode(nodeValue.left)
}
if ('right' in nodeValue) {
traverseNode(nodeValue.right)
}
if ('elements' in nodeValue) {
let elements = nodeValue.elements.filter(
currentNode => currentNode !== null,
)
for (let element of elements) {
traverseNode(element)
}
}
if ('argument' in nodeValue && nodeValue.argument) {
traverseNode(nodeValue.argument)
}
if ('arguments' in nodeValue) {
for (let argument of nodeValue.arguments) {
traverseNode(argument)
}
}
if ('properties' in nodeValue) {
for (let property of nodeValue.properties) {
traverseNode(property)
}
}
if ('expressions' in nodeValue) {
for (let nodeExpression of nodeValue.expressions) {
traverseNode(nodeExpression)
}
}
}
let traverseNode = nodeValue => {
checkNode(nodeValue)
}
traverseNode(init)
return dependencies
}
let formattedMembers = node.declarations.reduce(
(accumulator, declaration) => {
var _a, _b
let name
if (
declaration.id.type === 'ArrayPattern' ||
declaration.id.type === 'ObjectPattern'
) {
name = sourceCode.text.slice(...declaration.id.range)
} else {
;({ name } = declaration.id)
}
let dependencies = []
if (declaration.init) {
dependencies = extractDependencies(declaration.init)
}
let lastSortingNode =
(_a = accumulator.at(-1)) == null ? void 0 : _a.at(-1)
let sortingNode = {
isEslintDisabled: isNodeEslintDisabled.isNodeEslintDisabled(
declaration,
eslintDisabledLines,
),
size: rangeToDiff.rangeToDiff(declaration, sourceCode),
node: declaration,
dependencies,
name,
}
if (
(options.partitionByComment &&
isPartitionComment.hasPartitionComment(
options.partitionByComment,
getCommentsBefore.getCommentsBefore(declaration, sourceCode),
)) ||
(options.partitionByNewLine &&
lastSortingNode &&
getLinesBetween.getLinesBetween(
sourceCode,
lastSortingNode,
sortingNode,
))
) {
accumulator.push([])
}
;(_b = accumulator.at(-1)) == null ? void 0 : _b.push(sortingNode)
return accumulator
},
[[]],
)
let sortNodesIgnoringEslintDisabledNodes = ignoreEslintDisabledNodes =>
sortNodesByDependencies.sortNodesByDependencies(
formattedMembers.flatMap(nodes2 =>
sortNodes.sortNodes(nodes2, options, {
ignoreEslintDisabledNodes,
}),
),
{
ignoreEslintDisabledNodes,
},
)
let sortedNodes = sortNodesIgnoringEslintDisabledNodes(false)
let sortedNodesExcludingEslintDisabled =
sortNodesIgnoringEslintDisabledNodes(true)
let nodes = formattedMembers.flat()
pairwise.pairwise(nodes, (left, right) => {
let indexOfLeft = sortedNodes.indexOf(left)
let indexOfRight = sortedNodes.indexOf(right)
let indexOfRightExcludingEslintDisabled =
sortedNodesExcludingEslintDisabled.indexOf(right)
if (
indexOfLeft < indexOfRight &&
indexOfLeft < indexOfRightExcludingEslintDisabled
) {
return
}
let firstUnorderedNodeDependentOnRight =
sortNodesByDependencies.getFirstUnorderedNodeDependentOn(right, nodes)
context.report({
fix: fixer =>
makeFixes.makeFixes({
sortedNodes: sortedNodesExcludingEslintDisabled,
sourceCode,
options,
fixer,
nodes,
}),
data: {
nodeDependentOnRight:
firstUnorderedNodeDependentOnRight == null
? void 0
: firstUnorderedNodeDependentOnRight.name,
right: toSingleLine.toSingleLine(right.name),
left: toSingleLine.toSingleLine(left.name),
},
messageId: firstUnorderedNodeDependentOnRight
? 'unexpectedVariableDeclarationsDependencyOrder'
: 'unexpectedVariableDeclarationsOrder',
node: right.node,
})
})
},
}),
meta: {
type: 'suggestion',
docs: {
description: 'Enforce sorted variable declarations.',
},
fixable: 'code',
schema: [
{
type: 'object',
properties: {
type: {
description: 'Specifies the sorting method.',
type: 'string',
enum: ['alphabetical', 'natural', 'line-length'],
},
order: {
description:
'Determines whether the sorted items should be in ascending or descending order.',
type: 'string',
enum: ['asc', 'desc'],
},
matcher: {
description: 'Specifies the string matcher.',
type: 'string',
enum: ['minimatch', 'regex'],
},
ignoreCase: {
description:
'Controls whether sorting should be case-sensitive or not.',
type: 'boolean',
},
specialCharacters: {
description:
'Controls how special characters should be handled before sorting.',
type: 'string',
enum: ['remove', 'trim', 'keep'],
},
partitionByComment: {
...commonJsonSchemas.partitionByCommentJsonSchema,
description:
'Allows you to use comments to separate the variable declarations into logical groups.',
anyOf: [
{
type: 'array',
items: {
type: 'string',
},
},
{
type: 'boolean',
},
{
type: 'string',
},
],
},
partitionByNewLine: {
description:
'Allows to use spaces to separate the nodes into logical groups.',
type: 'boolean',
},
partitionByNewLine: commonJsonSchemas.partitionByNewLineJsonSchema,
specialCharacters: commonJsonSchemas.specialCharactersJsonSchema,
ignoreCase: commonJsonSchemas.ignoreCaseJsonSchema,
locales: commonJsonSchemas.localesJsonSchema,
order: commonJsonSchemas.orderJsonSchema,
type: commonJsonSchemas.typeJsonSchema,
},
additionalProperties: false,
type: 'object',
},
],
messages: {
unexpectedVariableDeclarationsDependencyOrder:
'Expected dependency "{{right}}" to come before "{{nodeDependentOnRight}}".',
unexpectedVariableDeclarationsOrder:
'Expected "{{right}}" to come before "{{left}}".',
unexpectedVariableDeclarationsDependencyOrder:
'Expected dependency "{{right}}" to come before "{{nodeDependentOnRight}}".',
},
docs: {
url: 'https://perfectionist.dev/rules/sort-variable-declarations',
description: 'Enforce sorted variable declarations.',
recommended: true,
},
type: 'suggestion',
fixable: 'code',
},
defaultOptions: [
{
type: 'alphabetical',
order: 'asc',
ignoreCase: true,
specialCharacters: 'keep',
matcher: 'minimatch',
partitionByComment: false,
partitionByNewLine: false,
},
],
create: context => ({
VariableDeclaration: node => {
if (node.declarations.length > 1) {
let settings = getSettings.getSettings(context.settings)
let options = complete.complete(context.options.at(0), settings, {
type: 'alphabetical',
ignoreCase: true,
specialCharacters: 'keep',
partitionByNewLine: false,
matcher: 'minimatch',
partitionByComment: false,
order: 'asc',
})
let sourceCode = getSourceCode.getSourceCode(context)
let partitionComment = options.partitionByComment
let extractDependencies = init => {
let dependencies = []
let checkNode = nodeValue => {
if (
nodeValue.type === 'ArrowFunctionExpression' ||
nodeValue.type === 'FunctionExpression'
) {
return
}
if (nodeValue.type === 'Identifier') {
dependencies.push(nodeValue.name)
}
if (nodeValue.type === 'Property') {
traverseNode(nodeValue.key)
traverseNode(nodeValue.value)
}
if (nodeValue.type === 'ConditionalExpression') {
traverseNode(nodeValue.test)
traverseNode(nodeValue.consequent)
traverseNode(nodeValue.alternate)
}
if (
'expression' in nodeValue &&
typeof nodeValue.expression !== 'boolean'
) {
traverseNode(nodeValue.expression)
}
if ('object' in nodeValue) {
traverseNode(nodeValue.object)
}
if ('callee' in nodeValue) {
traverseNode(nodeValue.callee)
}
if ('left' in nodeValue) {
traverseNode(nodeValue.left)
}
if ('right' in nodeValue) {
traverseNode(nodeValue.right)
}
if ('elements' in nodeValue) {
nodeValue.elements
.filter(currentNode => currentNode !== null)
.forEach(traverseNode)
}
if ('argument' in nodeValue && nodeValue.argument) {
traverseNode(nodeValue.argument)
}
if ('arguments' in nodeValue) {
nodeValue.arguments.forEach(traverseNode)
}
if ('properties' in nodeValue) {
nodeValue.properties.forEach(traverseNode)
}
if ('expressions' in nodeValue) {
nodeValue.expressions.forEach(traverseNode)
}
}
let traverseNode = nodeValue => {
checkNode(nodeValue)
}
traverseNode(init)
return dependencies
}
let formattedMembers = node.declarations.reduce(
(accumulator, declaration) => {
var _a, _b
let name
if (
declaration.id.type === 'ArrayPattern' ||
declaration.id.type === 'ObjectPattern'
) {
name = sourceCode.text.slice(...declaration.id.range)
} else {
;({ name } = declaration.id)
}
let dependencies = []
if (declaration.init) {
dependencies = extractDependencies(declaration.init)
}
let lastSortingNode =
(_a = accumulator.at(-1)) == null ? void 0 : _a.at(-1)
let sortingNode = {
size: rangeToDiff.rangeToDiff(declaration.range),
node: declaration,
dependencies,
name,
}
if (
(partitionComment &&
isPartitionComment.hasPartitionComment(
partitionComment,
getCommentsBefore.getCommentsBefore(declaration, sourceCode),
options.matcher,
)) ||
(options.partitionByNewLine &&
lastSortingNode &&
getLinesBetween.getLinesBetween(
sourceCode,
lastSortingNode,
sortingNode,
))
) {
accumulator.push([])
}
;(_b = accumulator.at(-1)) == null ? void 0 : _b.push(sortingNode)
return accumulator
},
[[]],
)
let sortedNodes = sortNodesByDependencies.sortNodesByDependencies(
formattedMembers
.map(nodes2 => sortNodes.sortNodes(nodes2, options))
.flat(),
)
let nodes = formattedMembers.flat()
pairwise.pairwise(nodes, (left, right) => {
let indexOfLeft = sortedNodes.indexOf(left)
let indexOfRight = sortedNodes.indexOf(right)
if (indexOfLeft > indexOfRight) {
let firstUnorderedNodeDependentOnRight =
sortNodesByDependencies.getFirstUnorderedNodeDependentOn(
right,
nodes,
)
context.report({
messageId: firstUnorderedNodeDependentOnRight
? 'unexpectedVariableDeclarationsDependencyOrder'
: 'unexpectedVariableDeclarationsOrder',
data: {
left: toSingleLine.toSingleLine(left.name),
right: toSingleLine.toSingleLine(right.name),
nodeDependentOnRight:
firstUnorderedNodeDependentOnRight == null
? void 0
: firstUnorderedNodeDependentOnRight.name,
},
node: right.node,
fix: fixer =>
makeFixes.makeFixes(
fixer,
nodes,
sortedNodes,
sourceCode,
options,
),
})
}
})
}
},
}),
name: 'sort-variable-declarations',
defaultOptions: [defaultOptions],
})
module.exports = sortVariableDeclarations
'use strict'
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
const naturalCompare = require('natural-compare-lite')
const naturalOrderby = require('natural-orderby')
let compare = (a, b, options) => {

@@ -9,3 +9,3 @@ let orderCoefficient = options.order === 'asc' ? 1 : -1

if (options.type === 'alphabetical') {
let formatString = getFormatStringFunc(
let formatString = getFormatStringFunction(
options.ignoreCase,

@@ -17,21 +17,18 @@ options.specialCharacters,

formatString(nodeValueGetter(bNode)),
options.locales,
)
} else if (options.type === 'natural') {
let prepareNumeric = string => {
let formattedNumberPattern = /^[+-]?[\d ,_]+(\.[\d ,_]+)?$/
if (formattedNumberPattern.test(string)) {
return string.replaceAll(/[ ,_]/g, '')
}
return string
}
sortingFunction = (aNode, bNode) => {
let formatString = getFormatStringFunc(
options.ignoreCase,
options.specialCharacters,
let naturalCompare = naturalOrderby.compare({
locale: options.locales.toString(),
order: options.order,
})
let formatString = getFormatStringFunction(
options.ignoreCase,
options.specialCharacters,
)
sortingFunction = (aNode, bNode) =>
naturalCompare(
formatString(nodeValueGetter(aNode)),
formatString(nodeValueGetter(bNode)),
)
return naturalCompare(
prepareNumeric(formatString(nodeValueGetter(aNode))),
prepareNumeric(formatString(nodeValueGetter(bNode))),
)
}
} else {

@@ -57,3 +54,3 @@ sortingFunction = (aNode, bNode) => {

}
let getFormatStringFunc = (ignoreCase, specialCharacters) => value => {
let getFormatStringFunction = (ignoreCase, specialCharacters) => value => {
let valueToCompare = value

@@ -65,10 +62,16 @@ if (ignoreCase) {

case 'remove':
valueToCompare = valueToCompare.replaceAll(/[^A-Za-zÀ-ž]+/g, '')
valueToCompare = valueToCompare.replaceAll(
/[^a-z\u{C0}-\u{24F}\u{1E00}-\u{1EFF}]+/giu,
'',
)
break
case 'trim':
valueToCompare = valueToCompare.replaceAll(/^[^A-Za-zÀ-ž]+/g, '')
valueToCompare = valueToCompare.replaceAll(
/^[^a-z\u{C0}-\u{24F}\u{1E00}-\u{1EFF}]+/giu,
'',
)
break
}
return valueToCompare.replaceAll(/\s/g, '')
return valueToCompare.replaceAll(/\s/gu, '')
}
exports.compare = compare
'use strict'
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
let complete = (options = {}, settings = {}, defaults) =>
Object.assign(defaults, settings, options)
let complete = (options = {}, settings = {}, defaults = {}) => ({
...defaults,
...settings,
...options,
})
exports.complete = complete

@@ -6,3 +6,3 @@ 'use strict'

filter: ({ value, type }) =>
!(type === 'Punctuator' && [',', ';'].includes(value)),
!(type === 'Punctuator' && [',', ';', ':'].includes(value)),
includeComments: true,

@@ -9,0 +9,0 @@ })

'use strict'
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
let getCommentsBefore = (node, source) =>
let getCommentsBefore = (node, source, tokenValueToIgnoreBefore) => {
let commentsBefore = getCommentsBeforeNodeOrToken(source, node)
let tokenBeforeNode = source.getTokenBefore(node)
if (
commentsBefore.length ||
!tokenValueToIgnoreBefore ||
(tokenBeforeNode == null ? void 0 : tokenBeforeNode.value) !==
tokenValueToIgnoreBefore
) {
return commentsBefore
}
return getCommentsBeforeNodeOrToken(source, tokenBeforeNode)
}
let getCommentsBeforeNodeOrToken = (source, node) =>
source.getCommentsBefore(node).filter(comment => {

@@ -5,0 +18,0 @@ let tokenBeforeComment = source.getTokenBefore(comment)

'use strict'
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
const utils = require('@typescript-eslint/utils')
const getEslintDisabledRules = require('./get-eslint-disabled-rules.js')
const isPartitionComment = require('./is-partition-comment.js')
const getCommentsBefore = require('./get-comments-before.js')
let getNodeRange = (node, sourceCode, additionalOptions) => {
var _a
let start = node.range.at(0)
let end = node.range.at(1)
let raw = sourceCode.text.slice(start, end)
if (utils.ASTUtils.isParenthesized(node, sourceCode)) {

@@ -23,14 +22,2 @@ let bodyOpeningParen = sourceCode.getTokenBefore(

}
if (raw.endsWith(';') || raw.endsWith(',')) {
let tokensAfter = sourceCode.getTokensAfter(node, {
includeComments: true,
count: 2,
})
if (
node.loc.start.line ===
((_a = tokensAfter.at(1)) == null ? void 0 : _a.loc.start.line)
) {
end -= 1
}
}
let comments = getCommentsBefore.getCommentsBefore(node, sourceCode)

@@ -41,14 +28,16 @@ let partitionComment =

: additionalOptions.partitionByComment) ?? false
let partitionCommentMatcher =
(additionalOptions == null ? void 0 : additionalOptions.matcher) ??
'minimatch'
let relevantTopComment
for (let i = comments.length - 1; i >= 0; i--) {
let comment = comments[i]
let eslintDisabledRules = getEslintDisabledRules.getEslintDisabledRules(
comment.value,
)
if (
isPartitionComment.isPartitionComment(
partitionComment,
comment.value,
partitionCommentMatcher,
)
isPartitionComment.isPartitionComment(partitionComment, comment.value) ||
(eslintDisabledRules == null
? void 0
: eslintDisabledRules.eslintDisableDirective) === 'eslint-disable' ||
(eslintDisabledRules == null
? void 0
: eslintDisabledRules.eslintDisableDirective) === 'eslint-enable'
) {

@@ -55,0 +44,0 @@ break

'use strict'
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
const getOptionsWithCleanGroups = options => ({
let getOptionsWithCleanGroups = options => ({
...options,

@@ -11,4 +11,4 @@ groups: options.groups

})
const getCleanedNestedGroups = nestedGroup =>
let getCleanedNestedGroups = nestedGroup =>
nestedGroup.length === 1 ? nestedGroup[0] : nestedGroup
exports.getOptionsWithCleanGroups = getOptionsWithCleanGroups

@@ -8,3 +8,3 @@ 'use strict'

let getInvalidOptions = object => {
let allowedOptions = [
let allowedOptions = /* @__PURE__ */ new Set([
'partitionByComment',

@@ -15,7 +15,7 @@ 'partitionByNewLine',

'ignoreCase',
'matcher',
'locales',
'order',
'type',
]
return Object.keys(object).filter(key => !allowedOptions.includes(key))
])
return Object.keys(object).filter(key => !allowedOptions.has(key))
}

@@ -26,3 +26,3 @@ let perfectionistSettings = settings.perfectionist

throw new Error(
'Invalid Perfectionist setting(s): ' + invalidOptions.join(', '),
`Invalid Perfectionist setting(s): ${invalidOptions.join(', ')}`,
)

@@ -29,0 +29,0 @@ }

@@ -6,4 +6,4 @@ 'use strict'

switch (node.type) {
case types.AST_NODE_TYPES.TSPropertySignature:
case types.AST_NODE_TYPES.TSMethodSignature:
case types.AST_NODE_TYPES.TSPropertySignature:
return node.optional

@@ -10,0 +10,0 @@ }

'use strict'
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
const getEslintDisabledRules = require('./get-eslint-disabled-rules.js')
const matches = require('./matches.js')
let isPartitionComment = (partitionComment, comment, matcher) =>
(Array.isArray(partitionComment) &&
partitionComment.some(pattern =>
matches.matches(comment.trim(), pattern, matcher),
)) ||
(typeof partitionComment === 'string' &&
matches.matches(comment.trim(), partitionComment, matcher)) ||
partitionComment === true
let hasPartitionComment = (partitionComment, comments, matcher) =>
comments.some(comment =>
isPartitionComment(partitionComment, comment.value, matcher),
let isPartitionComment = (partitionComment, comment) => {
if (getEslintDisabledRules.getEslintDisabledRules(comment)) {
return false
}
return (
(Array.isArray(partitionComment) &&
partitionComment.some(pattern =>
matches.matches(comment.trim(), pattern),
)) ||
(typeof partitionComment === 'string' &&
matches.matches(comment.trim(), partitionComment)) ||
partitionComment === true
)
}
let hasPartitionComment = (partitionComment, comments) =>
comments.some(comment => isPartitionComment(partitionComment, comment.value))
exports.hasPartitionComment = hasPartitionComment
exports.isPartitionComment = isPartitionComment
'use strict'
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
const getCommentAfter = require('./get-comment-after.js')
const makeCommentAfterFixes = require('./make-comment-after-fixes.js')
const getNodeRange = require('./get-node-range.js')
let makeFixes = (fixer, nodes, sortedNodes, source, additionalOptions) => {
var _a, _b
let makeFixes = ({ sortedNodes, sourceCode, options, fixer, nodes }) => {
let fixes = []
let isSingleline =
((_a = nodes.at(0)) == null ? void 0 : _a.node.loc.start.line) ===
((_b = nodes.at(-1)) == null ? void 0 : _b.node.loc.end.line)
for (let max = nodes.length, i = 0; i < max; i++) {
let { node } = nodes.at(i)
let sortingNode = nodes.at(i)
let sortedSortingNode = sortedNodes.at(i)
let { node } = sortingNode
let { addSafetySemicolonWhenInline, node: sortedNode } = sortedSortingNode
if (node === sortedNode) {
continue
}
let sortedNodeCode = sourceCode.text.slice(
...getNodeRange.getNodeRange(sortedNode, sourceCode, options),
)
let sortedNodeText = sourceCode.getText(sortedNode)
let tokensAfter = sourceCode.getTokensAfter(node, {
includeComments: false,
count: 1,
})
let nextToken = tokensAfter.at(0)
let sortedNextNodeEndsWithSafeCharacter =
sortedNodeText.endsWith(';') || sortedNodeText.endsWith(',')
let isNextTokenOnSameLineAsNode =
(nextToken == null ? void 0 : nextToken.loc.start.line) ===
node.loc.end.line
let isNextTokenSafeCharacter =
(nextToken == null ? void 0 : nextToken.value) === ';' ||
(nextToken == null ? void 0 : nextToken.value) === ','
if (
addSafetySemicolonWhenInline &&
isNextTokenOnSameLineAsNode &&
!sortedNextNodeEndsWithSafeCharacter &&
!isNextTokenSafeCharacter
) {
sortedNodeCode += ';'
}
fixes.push(
fixer.replaceTextRange(
getNodeRange.getNodeRange(node, source, additionalOptions),
source.text.slice(
...getNodeRange.getNodeRange(
sortedNodes.at(i).node,
source,
additionalOptions,
),
),
getNodeRange.getNodeRange(node, sourceCode, options),
sortedNodeCode,
),
)
let commentAfter = getCommentAfter.getCommentAfter(
sortedNodes.at(i).node,
source,
)
if (commentAfter && !isSingleline) {
let tokenBefore = source.getTokenBefore(commentAfter)
let range = [tokenBefore.range.at(1), commentAfter.range.at(1)]
fixes.push(fixer.replaceTextRange(range, ''))
let tokenAfterNode = source.getTokenAfter(node)
fixes.push(
fixer.insertTextAfter(
(tokenAfterNode == null ? void 0 : tokenAfterNode.loc.end.line) ===
node.loc.end.line
? tokenAfterNode
: node,
source.text.slice(...range),
),
)
}
fixes = [
...fixes,
...makeCommentAfterFixes.makeCommentAfterFixes({
sortedNode,
sourceCode,
fixer,
node,
}),
]
}

@@ -45,0 +56,0 @@ return fixes

'use strict'
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
const minimatch = require('minimatch')
let matches = (value, pattern, type) => {
switch (type) {
case 'regex':
return new RegExp(pattern).test(value)
case 'minimatch':
default:
return minimatch.minimatch(value, pattern, {
nocomment: true,
})
}
}
let matches = (value, pattern) => new RegExp(pattern).test(value)
exports.matches = matches
'use strict'
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
const isSortable = require('./is-sortable.js')
let pairwise = (nodes, callback) => {
if (nodes.length > 1) {
for (let i = 1; i < nodes.length; i++) {
let left = nodes.at(i - 1)
let right = nodes.at(i)
if (left && right) {
callback(left, right, i - 1)
}
if (!isSortable.isSortable(nodes)) {
return
}
for (let i = 1; i < nodes.length; i++) {
let left = nodes.at(i - 1)
let right = nodes.at(i)
if (left && right) {
callback(left, right, i - 1)
}

@@ -12,0 +14,0 @@ }

'use strict'
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
let rangeToDiff = range => {
let [from, to] = range
return to - from
let rangeToDiff = (node, sourceCode) => {
let nodeText = sourceCode.getText(node)
let endsWithCommaOrSemicolon =
nodeText.endsWith(';') || nodeText.endsWith(',')
let [from, to] = node.range
return to - from - (endsWithCommaOrSemicolon ? 1 : 0)
}
exports.rangeToDiff = rangeToDiff
'use strict'
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
let sortNodesByDependencies = nodes => {
let sortNodesByDependencies = (nodes, extraOptions) => {
let result = []
let visitedNodes = /* @__PURE__ */ new Set()
let inProcessNodes = /* @__PURE__ */ new Set()
let visitNode = node => {
if (visitedNodes.has(node)) {
let visitNode = sortingNode => {
if (visitedNodes.has(sortingNode)) {
return
}
if (inProcessNodes.has(node)) {
if (inProcessNodes.has(sortingNode)) {
return
}
inProcessNodes.add(node)
let dependentNodes = nodes.filter(n =>
node.dependencies.includes(n.dependencyName ?? n.name),
inProcessNodes.add(sortingNode)
let dependentNodes = nodes.filter(({ dependencyName, name }) =>
sortingNode.dependencies.includes(dependencyName ?? name),
)
for (let dependentNode of dependentNodes) {
visitNode(dependentNode)
if (
!(extraOptions == null
? void 0
: extraOptions.ignoreEslintDisabledNodes) ||
!dependentNode.isEslintDisabled
) {
visitNode(dependentNode)
}
}
visitedNodes.add(node)
inProcessNodes.delete(node)
result.push(node)
visitedNodes.add(sortingNode)
inProcessNodes.delete(sortingNode)
result.push(sortingNode)
}

@@ -25,0 +32,0 @@ for (let node of nodes) {

@@ -11,5 +11,9 @@ 'use strict'

if (
(_a = extraOptions == null ? void 0 : extraOptions.isNodeIgnored) == null
(sortingNode.isEslintDisabled &&
(extraOptions == null
? void 0
: extraOptions.ignoreEslintDisabledNodes)) ||
((_a = extraOptions == null ? void 0 : extraOptions.isNodeIgnored) == null
? void 0
: _a.call(extraOptions, sortingNode)
: _a.call(extraOptions, sortingNode))
) {

@@ -19,6 +23,6 @@ ignoredNodeIndices.push(index)

}
let groupNum = getGroupNumber.getGroupNumber(options.groups, sortingNode)
nodesByNonIgnoredGroupNumber[groupNum] =
nodesByNonIgnoredGroupNumber[groupNum] ?? []
nodesByNonIgnoredGroupNumber[groupNum].push(sortingNode)
let groupNumber = getGroupNumber.getGroupNumber(options.groups, sortingNode)
nodesByNonIgnoredGroupNumber[groupNumber] ??
(nodesByNonIgnoredGroupNumber[groupNumber] = [])
nodesByNonIgnoredGroupNumber[groupNumber].push(sortingNode)
}

@@ -25,0 +29,0 @@ let sortedNodes = []

'use strict'
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
const compare = require('./compare.js')
let sortNodes = (nodes, options) =>
[...nodes].sort((a, b) => compare.compare(a, b, options))
let sortNodes = (nodes, options, extraOptions) => {
let nonIgnoredNodes = []
let ignoredNodeIndices = []
for (let [index, sortingNode] of nodes.entries()) {
if (
sortingNode.isEslintDisabled &&
(extraOptions == null ? void 0 : extraOptions.ignoreEslintDisabledNodes)
) {
ignoredNodeIndices.push(index)
} else {
nonIgnoredNodes.push(sortingNode)
}
}
let sortedNodes = [...nonIgnoredNodes].sort((a, b) =>
compare.compare(a, b, options),
)
for (let ignoredIndex of ignoredNodeIndices) {
sortedNodes.splice(ignoredIndex, 0, nodes[ignoredIndex])
}
return sortedNodes
}
exports.sortNodes = sortNodes
'use strict'
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
let toSingleLine = string => string.replaceAll(/\s\s+/g, ' ').trim()
let toSingleLine = string => string.replaceAll(/\s{2,}/gu, ' ').trim()
exports.toSingleLine = toSingleLine
'use strict'
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
const matches = require('./matches.js')
let useGroups = ({ matcher, groups }) => {
let useGroups = ({ groups }) => {
let group

@@ -12,3 +12,3 @@ let groupsSet = new Set(groups.flat())

}
let setCustomGroups = (customGroups, name, params = {}) => {
let setCustomGroups = (customGroups, name, parameters = {}) => {
if (customGroups) {

@@ -18,13 +18,8 @@ for (let [key, pattern] of Object.entries(customGroups)) {

Array.isArray(pattern) &&
pattern.some(patternValue =>
matches.matches(name, patternValue, matcher),
)
pattern.some(patternValue => matches.matches(name, patternValue))
) {
defineGroup(key, params.override)
defineGroup(key, parameters.override)
}
if (
typeof pattern === 'string' &&
matches.matches(name, pattern, matcher)
) {
defineGroup(key, params.override)
if (typeof pattern === 'string' && matches.matches(name, pattern)) {
defineGroup(key, parameters.override)
}

@@ -31,0 +26,0 @@ }

@@ -16,3 +16,3 @@ 'use strict'

if (invalidGroups.length) {
throw new Error('Invalid group(s): ' + invalidGroups.join(', '))
throw new Error(`Invalid group(s): ${invalidGroups.join(', ')}`)
}

@@ -27,3 +27,3 @@ validateNoDuplicatedGroups(groups)

if (duplicatedGroups.length) {
throw new Error('Duplicated group(s): ' + duplicatedGroups.join(', '))
throw new Error(`Duplicated group(s): ${duplicatedGroups.join(', ')}`)
}

@@ -30,0 +30,0 @@ }

{
"name": "eslint-plugin-perfectionist",
"version": "4.0.0",
"description": "ESLint plugin for sorting various data such as objects, imports, types, enums, JSX props, etc.",
"version": "3.9.1",
"homepage": "https://perfectionist.dev",
"repository": "azat-io/eslint-plugin-perfectionist",
"author": "Azat S. <to@azat.io>",
"license": "MIT",
"keywords": [

@@ -14,18 +10,10 @@ "eslint",

],
"engines": {
"node": "^18.0.0 || >=20.0.0"
},
"publishConfig": {
"access": "public"
},
"files": [
"./dist"
],
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"homepage": "https://perfectionist.dev",
"repository": "azat-io/eslint-plugin-perfectionist",
"license": "MIT",
"author": "Azat S. <to@azat.io>",
"type": "commonjs",
"exports": {
".": {
"require": "./dist/index.js",
"import": "./dist/index.mjs",
"default": "./dist/index.js",
"types": "./dist/index.d.ts"

@@ -35,29 +23,20 @@ },

},
"peerDependenciesMeta": {
"astro-eslint-parser": {
"optional": true
},
"svelte": {
"optional": true
},
"svelte-eslint-parser": {
"optional": true
},
"vue-eslint-parser": {
"optional": true
}
"files": [
"./dist"
],
"dependencies": {
"@azat-io/eslint-config": "^2.1.2",
"@typescript-eslint/types": "^8.15.0",
"@typescript-eslint/utils": "^8.15.0",
"natural-orderby": "^5.0.0"
},
"peerDependencies": {
"astro-eslint-parser": "^1.0.2",
"eslint": ">=8.0.0",
"svelte": ">=3.0.0",
"svelte-eslint-parser": "^0.41.1",
"vue-eslint-parser": ">=9.0.0"
"eslint": ">=8.0.0"
},
"dependencies": {
"@typescript-eslint/types": "^8.9.0",
"@typescript-eslint/utils": "^8.9.0",
"minimatch": "^9.0.5",
"natural-compare-lite": "^1.4.0"
"engines": {
"node": "^18.0.0 || >=20.0.0"
},
"publishConfig": {
"access": "public"
}
}

@@ -16,5 +16,5 @@ # ESLint Plugin Perfectionist

ESLint plugin that sets rules to format your code and make it consistent.
An ESLint plugin that sets rules to format your code and make it consistent.
This plugin defines rules for sorting various data, such as objects, imports, TypeScript types, enums, JSX props, Svelte attributes, etc. alphabetically, naturally, or by line length
This plugin defines rules for sorting various data, such as objects, imports, TypeScript types, enums, JSX props, Svelte attributes, etc. alphabetically, naturally, or by line length.

@@ -31,3 +31,3 @@ All rules are automatically fixable. It's safe!

- **Code Review and Collaboration**: If you set rules that say you can only do things one way, then no one will have to spend time thinking about how to do it.
- **Code Review and Collaboration**: If you set rules that say you can only do things one way, no one will have to spend time thinking about how to do it.

@@ -177,24 +177,24 @@ - **Code Uniformity**: When all code looks exactly the same, it is very hard to see who wrote it, which makes achieving the lofty goal of _collective code ownership_ easier.

| Name | Description | 🔧 |
| :--------------------------------------------------------------------------------------- | :------------------------------------------ | :-- |
| [sort-array-includes](https://perfectionist.dev/rules/sort-array-includes) | Enforce sorted arrays before include method | 🔧 |
| [sort-astro-attributes](https://perfectionist.dev/rules/sort-astro-attributes) | Enforce sorted Astro attributes | 🔧 |
| [sort-classes](https://perfectionist.dev/rules/sort-classes) | Enforce sorted classes | 🔧 |
| [sort-enums](https://perfectionist.dev/rules/sort-enums) | Enforce sorted TypeScript enums | 🔧 |
| [sort-exports](https://perfectionist.dev/rules/sort-exports) | Enforce sorted exports | 🔧 |
| [sort-imports](https://perfectionist.dev/rules/sort-imports) | Enforce sorted imports | 🔧 |
| [sort-interfaces](https://perfectionist.dev/rules/sort-interfaces) | Enforce sorted interface properties | 🔧 |
| [sort-intersection-types](https://perfectionist.dev/rules/sort-intersection-types) | Enforce sorted intersection types | 🔧 |
| [sort-jsx-props](https://perfectionist.dev/rules/sort-jsx-props) | Enforce sorted JSX props | 🔧 |
| [sort-maps](https://perfectionist.dev/rules/sort-maps) | Enforce sorted Map elements | 🔧 |
| [sort-named-exports](https://perfectionist.dev/rules/sort-named-exports) | Enforce sorted named exports | 🔧 |
| [sort-named-imports](https://perfectionist.dev/rules/sort-named-imports) | Enforce sorted named imports | 🔧 |
| [sort-object-types](https://perfectionist.dev/rules/sort-object-types) | Enforce sorted object types | 🔧 |
| [sort-objects](https://perfectionist.dev/rules/sort-objects) | Enforce sorted objects | 🔧 |
| [sort-sets](https://perfectionist.dev/rules/sort-sets) | Enforce sorted Set elements | 🔧 |
| [sort-svelte-attributes](https://perfectionist.dev/rules/sort-svelte-attributes) | Enforce sorted Svelte attributes | 🔧 |
| [sort-switch-case](https://perfectionist.dev/rules/sort-switch-case) | Enforce sorted switch case statements | 🔧 |
| [sort-union-types](https://perfectionist.dev/rules/sort-union-types) | Enforce sorted union types | 🔧 |
| [sort-variable-declarations](https://perfectionist.dev/rules/sort-variable-declarations) | Enforce sorted variable declarations | 🔧 |
| [sort-vue-attributes](https://perfectionist.dev/rules/sort-vue-attributes) | Enforce sorted Vue attributes | 🔧 |
| Name | Description | 🔧 |
| :--------------------------------------------------------------------------------------- | :-------------------------------------------- | :-- |
| [sort-array-includes](https://perfectionist.dev/rules/sort-array-includes) | Enforce sorted arrays before include method | 🔧 |
| [sort-classes](https://perfectionist.dev/rules/sort-classes) | Enforce sorted classes | 🔧 |
| [sort-decorators](https://perfectionist.dev/rules/sort-decorators) | Enforce sorted decorators | 🔧 |
| [sort-enums](https://perfectionist.dev/rules/sort-enums) | Enforce sorted TypeScript enums | 🔧 |
| [sort-exports](https://perfectionist.dev/rules/sort-exports) | Enforce sorted exports | 🔧 |
| [sort-heritage-clauses](https://perfectionist.dev/rules/sort-heritage-clauses) | Enforce sorted `implements`/`extends` clauses | 🔧 |
| [sort-imports](https://perfectionist.dev/rules/sort-imports) | Enforce sorted imports | 🔧 |
| [sort-interfaces](https://perfectionist.dev/rules/sort-interfaces) | Enforce sorted interface properties | 🔧 |
| [sort-intersection-types](https://perfectionist.dev/rules/sort-intersection-types) | Enforce sorted intersection types | 🔧 |
| [sort-jsx-props](https://perfectionist.dev/rules/sort-jsx-props) | Enforce sorted JSX props | 🔧 |
| [sort-maps](https://perfectionist.dev/rules/sort-maps) | Enforce sorted Map elements | 🔧 |
| [sort-modules](https://perfectionist.dev/rules/sort-modules) | Enforce sorted modules | 🔧 |
| [sort-named-exports](https://perfectionist.dev/rules/sort-named-exports) | Enforce sorted named exports | 🔧 |
| [sort-named-imports](https://perfectionist.dev/rules/sort-named-imports) | Enforce sorted named imports | 🔧 |
| [sort-object-types](https://perfectionist.dev/rules/sort-object-types) | Enforce sorted object types | 🔧 |
| [sort-objects](https://perfectionist.dev/rules/sort-objects) | Enforce sorted objects | 🔧 |
| [sort-sets](https://perfectionist.dev/rules/sort-sets) | Enforce sorted Set elements | 🔧 |
| [sort-switch-case](https://perfectionist.dev/rules/sort-switch-case) | Enforce sorted switch case statements | 🔧 |
| [sort-union-types](https://perfectionist.dev/rules/sort-union-types) | Enforce sorted union types | 🔧 |
| [sort-variable-declarations](https://perfectionist.dev/rules/sort-variable-declarations) | Enforce sorted variable declarations | 🔧 |

@@ -209,3 +209,3 @@ <!-- end auto-generated rules list -->

### Is it safety?
### Is it safe?

@@ -212,0 +212,0 @@ On the whole, yes. We are very careful to make sure that the work of the plugin does not negatively affect the work of the code. For example, the plugin takes into account spread operators in JSX and objects, comments to the code. Safety is our priority. If you encounter any problem, you can create an [issue](https://github.com/azat-io/eslint-plugin-perfectionist/issues/new/choose).

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc