eslint-plugin-perfectionist 3.3.0 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/index.d.ts +243 -21
  2. package/dist/index.js +3 -1
  3. package/dist/index.mjs +2 -0
  4. package/dist/rules/sort-array-includes.js +108 -107
  5. package/dist/rules/sort-array-includes.mjs +100 -100
  6. package/dist/rules/sort-astro-attributes.js +6 -0
  7. package/dist/rules/sort-astro-attributes.mjs +6 -0
  8. package/dist/rules/sort-classes-utils.js +71 -0
  9. package/dist/rules/sort-classes-utils.mjs +75 -1
  10. package/dist/rules/sort-classes.js +234 -79
  11. package/dist/rules/sort-classes.mjs +232 -69
  12. package/dist/rules/sort-classes.types.js +71 -0
  13. package/dist/rules/sort-classes.types.mjs +71 -0
  14. package/dist/rules/sort-enums.js +11 -15
  15. package/dist/rules/sort-enums.mjs +11 -11
  16. package/dist/rules/sort-imports.js +46 -5
  17. package/dist/rules/sort-imports.mjs +46 -5
  18. package/dist/rules/sort-interfaces.js +6 -0
  19. package/dist/rules/sort-interfaces.mjs +6 -0
  20. package/dist/rules/sort-intersection-types.js +20 -0
  21. package/dist/rules/sort-intersection-types.mjs +20 -0
  22. package/dist/rules/sort-jsx-props.js +6 -0
  23. package/dist/rules/sort-jsx-props.mjs +6 -0
  24. package/dist/rules/sort-object-types.js +6 -0
  25. package/dist/rules/sort-object-types.mjs +6 -0
  26. package/dist/rules/sort-objects.js +34 -43
  27. package/dist/rules/sort-objects.mjs +28 -30
  28. package/dist/rules/sort-sets.js +48 -0
  29. package/dist/rules/sort-sets.mjs +47 -0
  30. package/dist/rules/sort-svelte-attributes.js +6 -0
  31. package/dist/rules/sort-svelte-attributes.mjs +6 -0
  32. package/dist/rules/sort-union-types.js +20 -0
  33. package/dist/rules/sort-union-types.mjs +20 -0
  34. package/dist/rules/sort-variable-declarations.js +43 -14
  35. package/dist/rules/sort-variable-declarations.mjs +41 -10
  36. package/dist/rules/sort-vue-attributes.js +6 -0
  37. package/dist/rules/sort-vue-attributes.mjs +6 -0
  38. package/dist/utils/compare.js +0 -6
  39. package/dist/utils/compare.mjs +0 -6
  40. package/dist/utils/sort-nodes-by-dependencies.js +30 -0
  41. package/dist/utils/sort-nodes-by-dependencies.mjs +28 -0
  42. package/dist/utils/validate-groups-configuration.js +19 -0
  43. package/dist/utils/validate-groups-configuration.mjs +17 -0
  44. package/package.json +4 -4
  45. package/readme.md +1 -0
@@ -10,6 +10,32 @@ import { makeFixes } from '../utils/make-fixes.mjs'
10
10
  import { complete } from '../utils/complete.mjs'
11
11
  import { pairwise } from '../utils/pairwise.mjs'
12
12
  import { compare } from '../utils/compare.mjs'
13
+ let jsonSchema = {
14
+ type: 'object',
15
+ properties: {
16
+ type: {
17
+ description: 'Specifies the sorting method.',
18
+ type: 'string',
19
+ enum: ['alphabetical', 'natural', 'line-length'],
20
+ },
21
+ order: {
22
+ description:
23
+ 'Determines whether the sorted items should be in ascending or descending order.',
24
+ type: 'string',
25
+ enum: ['asc', 'desc'],
26
+ },
27
+ ignoreCase: {
28
+ description: 'Controls whether sorting should be case-sensitive or not.',
29
+ type: 'boolean',
30
+ },
31
+ groupKind: {
32
+ description: 'Specifies top-level groups.',
33
+ enum: ['mixed', 'literals-first', 'spreads-first'],
34
+ type: 'string',
35
+ },
36
+ },
37
+ additionalProperties: false,
38
+ }
13
39
  const sortArrayIncludes = createEslintRule({
14
40
  name: 'sort-array-includes',
15
41
  meta: {
@@ -18,35 +44,7 @@ const sortArrayIncludes = createEslintRule({
18
44
  description: 'Enforce sorted arrays before include method.',
19
45
  },
20
46
  fixable: 'code',
21
- schema: [
22
- {
23
- type: 'object',
24
- properties: {
25
- type: {
26
- description: 'Specifies the sorting method.',
27
- type: 'string',
28
- enum: ['alphabetical', 'natural', 'line-length'],
29
- },
30
- order: {
31
- description:
32
- 'Determines whether the sorted items should be in ascending or descending order.',
33
- type: 'string',
34
- enum: ['asc', 'desc'],
35
- },
36
- ignoreCase: {
37
- description:
38
- 'Controls whether sorting should be case-sensitive or not.',
39
- type: 'boolean',
40
- },
41
- groupKind: {
42
- description: 'Specifies top-level groups.',
43
- enum: ['mixed', 'literals-first', 'spreads-first'],
44
- type: 'string',
45
- },
46
- },
47
- additionalProperties: false,
48
- },
49
- ],
47
+ schema: [jsonSchema],
50
48
  messages: {
51
49
  unexpectedArrayIncludesOrder:
52
50
  'Expected "{{right}}" to come before "{{left}}".',
@@ -72,78 +70,80 @@ const sortArrayIncludes = createEslintRule({
72
70
  node.object.type === 'ArrayExpression'
73
71
  ? node.object.elements
74
72
  : node.object.arguments
75
- let settings = getSettings(context.settings)
76
- if (elements.length > 1) {
77
- let options = complete(context.options.at(0), settings, {
78
- groupKind: 'literals-first',
79
- type: 'alphabetical',
80
- ignoreCase: true,
81
- order: 'asc',
82
- })
83
- let sourceCode = getSourceCode(context)
84
- let nodes = elements
85
- .reduce(
86
- (accumulator, element) => {
87
- if (element !== null) {
88
- let group = 'unknown'
89
- if (typeof options.groupKind === 'string') {
90
- group =
91
- element.type === 'SpreadElement' ? 'spread' : 'literal'
92
- }
93
- accumulator.at(0).push({
94
- name:
95
- element.type === 'Literal'
96
- ? `${element.value}`
97
- : sourceCode.text.slice(...element.range),
98
- size: rangeToDiff(element.range),
99
- type: element.type,
100
- node: element,
101
- group,
102
- })
103
- }
104
- return accumulator
105
- },
106
- [[], []],
107
- )
108
- .flat()
109
- pairwise(nodes, (left, right) => {
110
- let groupKindOrder = ['unknown']
111
- if (typeof options.groupKind === 'string') {
112
- groupKindOrder =
113
- options.groupKind === 'literals-first'
114
- ? ['literal', 'spread']
115
- : ['spread', 'literal']
116
- }
117
- let leftNum = getGroupNumber(groupKindOrder, left)
118
- let rightNum = getGroupNumber(groupKindOrder, right)
119
- if (
120
- (options.groupKind !== 'mixed' && leftNum > rightNum) ||
121
- ((options.groupKind === 'mixed' || leftNum === rightNum) &&
122
- isPositive(compare(left, right, options)))
123
- ) {
124
- context.report({
125
- messageId: 'unexpectedArrayIncludesOrder',
126
- data: {
127
- left: toSingleLine(left.name),
128
- right: toSingleLine(right.name),
129
- },
130
- node: right.node,
131
- fix: fixer => {
132
- let sortedNodes =
133
- options.groupKind !== 'mixed'
134
- ? groupKindOrder
135
- .map(group => nodes.filter(n => n.group === group))
136
- .map(groupedNodes => sortNodes(groupedNodes, options))
137
- .flat()
138
- : sortNodes(nodes, options)
139
- return makeFixes(fixer, nodes, sortedNodes, sourceCode)
140
- },
141
- })
142
- }
143
- })
144
- }
73
+ sortArray(context, 'unexpectedArrayIncludesOrder', elements)
145
74
  }
146
75
  },
147
76
  }),
148
77
  })
149
- export { sortArrayIncludes as default }
78
+ let sortArray = (context, messageId, elements) => {
79
+ let settings = getSettings(context.settings)
80
+ if (elements.length > 1) {
81
+ let options = complete(context.options.at(0), settings, {
82
+ groupKind: 'literals-first',
83
+ type: 'alphabetical',
84
+ ignoreCase: true,
85
+ order: 'asc',
86
+ })
87
+ let sourceCode = getSourceCode(context)
88
+ let nodes = elements
89
+ .reduce(
90
+ (accumulator, element) => {
91
+ if (element !== null) {
92
+ let group = 'unknown'
93
+ if (typeof options.groupKind === 'string') {
94
+ group = element.type === 'SpreadElement' ? 'spread' : 'literal'
95
+ }
96
+ accumulator.at(0).push({
97
+ name:
98
+ element.type === 'Literal'
99
+ ? `${element.value}`
100
+ : sourceCode.text.slice(...element.range),
101
+ size: rangeToDiff(element.range),
102
+ type: element.type,
103
+ node: element,
104
+ group,
105
+ })
106
+ }
107
+ return accumulator
108
+ },
109
+ [[], []],
110
+ )
111
+ .flat()
112
+ pairwise(nodes, (left, right) => {
113
+ let groupKindOrder = ['unknown']
114
+ if (typeof options.groupKind === 'string') {
115
+ groupKindOrder =
116
+ options.groupKind === 'literals-first'
117
+ ? ['literal', 'spread']
118
+ : ['spread', 'literal']
119
+ }
120
+ let leftNum = getGroupNumber(groupKindOrder, left)
121
+ let rightNum = getGroupNumber(groupKindOrder, right)
122
+ if (
123
+ (options.groupKind !== 'mixed' && leftNum > rightNum) ||
124
+ ((options.groupKind === 'mixed' || leftNum === rightNum) &&
125
+ isPositive(compare(left, right, options)))
126
+ ) {
127
+ context.report({
128
+ messageId,
129
+ data: {
130
+ left: toSingleLine(left.name),
131
+ right: toSingleLine(right.name),
132
+ },
133
+ node: right.node,
134
+ fix: fixer => {
135
+ let sortedNodes =
136
+ options.groupKind !== 'mixed'
137
+ ? groupKindOrder
138
+ .map(group => nodes.filter(n => n.group === group))
139
+ .map(groupedNodes => sortNodes(groupedNodes, options))
140
+ .flat()
141
+ : sortNodes(nodes, options)
142
+ return makeFixes(fixer, nodes, sortedNodes, sourceCode)
143
+ },
144
+ })
145
+ }
146
+ })
147
+ }
148
+ }
149
+ export { sortArrayIncludes as default, jsonSchema, sortArray }
@@ -1,5 +1,6 @@
1
1
  'use strict'
2
2
  const path = require('node:path')
3
+ const validateGroupsConfiguration = require('../utils/validate-groups-configuration.js')
3
4
  const createEslintRule = require('../utils/create-eslint-rule.js')
4
5
  const getGroupNumber = require('../utils/get-group-number.js')
5
6
  const getSourceCode = require('../utils/get-source-code.js')
@@ -109,6 +110,11 @@ const sortAstroAttributes = createEslintRule.createEslintRule({
109
110
  order: 'asc',
110
111
  groups: [],
111
112
  })
113
+ validateGroupsConfiguration.validateGroupsConfiguration(
114
+ options.groups,
115
+ ['astro-shorthand', 'multiline', 'shorthand', 'unknown'],
116
+ Object.keys(options.customGroups),
117
+ )
112
118
  let sourceCode = getSourceCode.getSourceCode(context)
113
119
  let parts = attributes.reduce(
114
120
  (accumulator, attribute) => {
@@ -1,4 +1,5 @@
1
1
  import path from 'node:path'
2
+ import { validateGroupsConfiguration } from '../utils/validate-groups-configuration.mjs'
2
3
  import { createEslintRule } from '../utils/create-eslint-rule.mjs'
3
4
  import { getGroupNumber } from '../utils/get-group-number.mjs'
4
5
  import { getSourceCode } from '../utils/get-source-code.mjs'
@@ -108,6 +109,11 @@ const sortAstroAttributes = createEslintRule({
108
109
  order: 'asc',
109
110
  groups: [],
110
111
  })
112
+ validateGroupsConfiguration(
113
+ options.groups,
114
+ ['astro-shorthand', 'multiline', 'shorthand', 'unknown'],
115
+ Object.keys(options.customGroups),
116
+ )
111
117
  let sourceCode = getSourceCode(context)
112
118
  let parts = attributes.reduce(
113
119
  (accumulator, attribute) => {
@@ -1,5 +1,6 @@
1
1
  'use strict'
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
3
+ const minimatch = require('minimatch')
3
4
  const cachedGroupsByModifiersAndSelectors = /* @__PURE__ */ new Map()
4
5
  const generateOfficialGroups = (modifiers, selectors) => {
5
6
  let modifiersAndSelectorsKey = modifiers.join('&') + '/' + selectors.join('&')
@@ -95,5 +96,75 @@ const getOverloadSignatureGroups = members => {
95
96
  ...staticOverloadSignaturesByName.values(),
96
97
  ].filter(group => group.length > 1)
97
98
  }
99
+ const customGroupMatches = props => {
100
+ if ('anyOf' in props.customGroup) {
101
+ return props.customGroup.anyOf.some(subgroup =>
102
+ customGroupMatches({ ...props, customGroup: subgroup }),
103
+ )
104
+ }
105
+ if (
106
+ props.customGroup.selector &&
107
+ !props.selectors.includes(props.customGroup.selector)
108
+ ) {
109
+ return false
110
+ }
111
+ if (props.customGroup.modifiers) {
112
+ for (let modifier of props.customGroup.modifiers) {
113
+ if (!props.modifiers.includes(modifier)) {
114
+ return false
115
+ }
116
+ }
117
+ }
118
+ if (
119
+ 'elementNamePattern' in props.customGroup &&
120
+ props.customGroup.elementNamePattern
121
+ ) {
122
+ let matchesElementNamePattern = minimatch.minimatch(
123
+ props.elementName,
124
+ props.customGroup.elementNamePattern,
125
+ {
126
+ nocomment: true,
127
+ },
128
+ )
129
+ if (!matchesElementNamePattern) {
130
+ return false
131
+ }
132
+ }
133
+ if (
134
+ 'decoratorNamePattern' in props.customGroup &&
135
+ props.customGroup.decoratorNamePattern
136
+ ) {
137
+ let decoratorPattern = props.customGroup.decoratorNamePattern
138
+ let matchesDecoratorNamePattern = props.decorators.some(decorator =>
139
+ minimatch.minimatch(decorator, decoratorPattern, {
140
+ nocomment: true,
141
+ }),
142
+ )
143
+ if (!matchesDecoratorNamePattern) {
144
+ return false
145
+ }
146
+ }
147
+ return true
148
+ }
149
+ const getCompareOptions = (options, groupNumber) => {
150
+ let group = options.groups[groupNumber]
151
+ let customGroup =
152
+ typeof group === 'string' && Array.isArray(options.customGroups)
153
+ ? options.customGroups.find(g => group === g.groupName)
154
+ : null
155
+ if ((customGroup == null ? void 0 : customGroup.type) === 'unsorted') {
156
+ return null
157
+ }
158
+ return {
159
+ type: (customGroup == null ? void 0 : customGroup.type) ?? options.type,
160
+ order:
161
+ customGroup && 'order' in customGroup && customGroup.order
162
+ ? customGroup.order
163
+ : options.order,
164
+ ignoreCase: options.ignoreCase,
165
+ }
166
+ }
167
+ exports.customGroupMatches = customGroupMatches
98
168
  exports.generateOfficialGroups = generateOfficialGroups
169
+ exports.getCompareOptions = getCompareOptions
99
170
  exports.getOverloadSignatureGroups = getOverloadSignatureGroups
@@ -1,3 +1,4 @@
1
+ import { minimatch } from 'minimatch'
1
2
  const cachedGroupsByModifiersAndSelectors = /* @__PURE__ */ new Map()
2
3
  const generateOfficialGroups = (modifiers, selectors) => {
3
4
  let modifiersAndSelectorsKey = modifiers.join('&') + '/' + selectors.join('&')
@@ -93,4 +94,77 @@ const getOverloadSignatureGroups = members => {
93
94
  ...staticOverloadSignaturesByName.values(),
94
95
  ].filter(group => group.length > 1)
95
96
  }
96
- export { generateOfficialGroups, getOverloadSignatureGroups }
97
+ const customGroupMatches = props => {
98
+ if ('anyOf' in props.customGroup) {
99
+ return props.customGroup.anyOf.some(subgroup =>
100
+ customGroupMatches({ ...props, customGroup: subgroup }),
101
+ )
102
+ }
103
+ if (
104
+ props.customGroup.selector &&
105
+ !props.selectors.includes(props.customGroup.selector)
106
+ ) {
107
+ return false
108
+ }
109
+ if (props.customGroup.modifiers) {
110
+ for (let modifier of props.customGroup.modifiers) {
111
+ if (!props.modifiers.includes(modifier)) {
112
+ return false
113
+ }
114
+ }
115
+ }
116
+ if (
117
+ 'elementNamePattern' in props.customGroup &&
118
+ props.customGroup.elementNamePattern
119
+ ) {
120
+ let matchesElementNamePattern = minimatch(
121
+ props.elementName,
122
+ props.customGroup.elementNamePattern,
123
+ {
124
+ nocomment: true,
125
+ },
126
+ )
127
+ if (!matchesElementNamePattern) {
128
+ return false
129
+ }
130
+ }
131
+ if (
132
+ 'decoratorNamePattern' in props.customGroup &&
133
+ props.customGroup.decoratorNamePattern
134
+ ) {
135
+ let decoratorPattern = props.customGroup.decoratorNamePattern
136
+ let matchesDecoratorNamePattern = props.decorators.some(decorator =>
137
+ minimatch(decorator, decoratorPattern, {
138
+ nocomment: true,
139
+ }),
140
+ )
141
+ if (!matchesDecoratorNamePattern) {
142
+ return false
143
+ }
144
+ }
145
+ return true
146
+ }
147
+ const getCompareOptions = (options, groupNumber) => {
148
+ let group = options.groups[groupNumber]
149
+ let customGroup =
150
+ typeof group === 'string' && Array.isArray(options.customGroups)
151
+ ? options.customGroups.find(g => group === g.groupName)
152
+ : null
153
+ if ((customGroup == null ? void 0 : customGroup.type) === 'unsorted') {
154
+ return null
155
+ }
156
+ return {
157
+ type: (customGroup == null ? void 0 : customGroup.type) ?? options.type,
158
+ order:
159
+ customGroup && 'order' in customGroup && customGroup.order
160
+ ? customGroup.order
161
+ : options.order,
162
+ ignoreCase: options.ignoreCase,
163
+ }
164
+ }
165
+ export {
166
+ customGroupMatches,
167
+ generateOfficialGroups,
168
+ getCompareOptions,
169
+ getOverloadSignatureGroups,
170
+ }