eslint-plugin-perfectionist 4.2.0 → 4.4.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 (42) hide show
  1. package/dist/alphabet.d.ts +176 -0
  2. package/dist/index.d.ts +2 -0
  3. package/dist/index.js +8 -0
  4. package/dist/rules/sort-array-includes-utils.js +30 -0
  5. package/dist/rules/sort-array-includes.js +150 -54
  6. package/dist/rules/sort-array-includes.types.js +10 -0
  7. package/dist/rules/sort-classes.js +19 -16
  8. package/dist/rules/sort-decorators.js +11 -6
  9. package/dist/rules/sort-enums.js +39 -31
  10. package/dist/rules/sort-exports.js +11 -5
  11. package/dist/rules/sort-heritage-clauses.js +12 -6
  12. package/dist/rules/sort-imports.js +34 -21
  13. package/dist/rules/sort-interfaces.js +3 -1
  14. package/dist/rules/sort-intersection-types.js +24 -19
  15. package/dist/rules/sort-jsx-props.js +12 -7
  16. package/dist/rules/sort-maps.js +18 -13
  17. package/dist/rules/sort-modules.js +23 -18
  18. package/dist/rules/sort-named-exports.js +18 -13
  19. package/dist/rules/sort-named-imports.js +18 -13
  20. package/dist/rules/sort-object-types.js +149 -103
  21. package/dist/rules/sort-objects.js +142 -58
  22. package/dist/rules/sort-sets.js +14 -5
  23. package/dist/rules/sort-switch-case.js +16 -14
  24. package/dist/rules/sort-union-types.js +24 -19
  25. package/dist/rules/sort-variable-declarations.js +18 -13
  26. package/dist/utils/alphabet.js +385 -0
  27. package/dist/utils/common-json-schemas.js +27 -5
  28. package/dist/utils/compare.js +88 -38
  29. package/dist/utils/convert-boolean-to-sign.js +4 -0
  30. package/dist/utils/create-node-index-map.js +10 -0
  31. package/dist/{rules → utils}/get-custom-groups-compare-options.js +1 -0
  32. package/dist/utils/{get-node-parent.js → get-first-node-parent-with-type.js} +3 -4
  33. package/dist/utils/get-matching-context-options.js +18 -0
  34. package/dist/utils/get-settings.js +1 -0
  35. package/dist/utils/is-node-function-type.js +15 -0
  36. package/dist/utils/is-partition-comment.js +4 -1
  37. package/dist/utils/pairwise.js +1 -3
  38. package/dist/utils/validate-custom-sort-configuration.js +11 -0
  39. package/dist/{rules → utils}/validate-generated-groups-configuration.js +1 -1
  40. package/dist/utils/validate-groups-configuration.js +11 -5
  41. package/package.json +7 -3
  42. package/readme.md +4 -3
@@ -20,20 +20,29 @@ const sortSets = createEslintRule.createEslintRule({
20
20
  node.arguments[0].type === 'ArrayExpression'
21
21
  ? node.arguments[0].elements
22
22
  : node.arguments[0].arguments
23
- sortArrayIncludes.sortArray(context, 'unexpectedSetsOrder', elements)
23
+ sortArrayIncludes.sortArray({
24
+ availableMessageIds: {
25
+ unexpectedGroupOrder: 'unexpectedSetsGroupOrder',
26
+ unexpectedOrder: 'unexpectedSetsOrder',
27
+ },
28
+ elements,
29
+ context,
30
+ })
24
31
  }
25
32
  },
26
33
  }),
27
34
  meta: {
35
+ messages: {
36
+ unexpectedSetsGroupOrder:
37
+ 'Expected "{{right}}" ({{rightGroup}}) to come before "{{left}}" ({{leftGroup}}).',
38
+ unexpectedSetsOrder: 'Expected "{{right}}" to come before "{{left}}".',
39
+ },
28
40
  docs: {
29
41
  url: 'https://perfectionist.dev/rules/sort-sets',
30
42
  description: 'Enforce sorted sets.',
31
43
  recommended: true,
32
44
  },
33
- messages: {
34
- unexpectedSetsOrder: 'Expected "{{right}}" to come before "{{left}}".',
35
- },
36
- schema: [sortArrayIncludes.jsonSchema],
45
+ schema: sortArrayIncludes.jsonSchema,
37
46
  type: 'suggestion',
38
47
  fixable: 'code',
39
48
  },
@@ -1,6 +1,8 @@
1
1
  'use strict'
2
2
  const commonJsonSchemas = require('../utils/common-json-schemas.js')
3
+ const validateCustomSortConfiguration = require('../utils/validate-custom-sort-configuration.js')
3
4
  const makeCommentAfterFixes = require('../utils/make-comment-after-fixes.js')
5
+ const createNodeIndexMap = require('../utils/create-node-index-map.js')
4
6
  const createEslintRule = require('../utils/create-eslint-rule.js')
5
7
  const getSourceCode = require('../utils/get-source-code.js')
6
8
  const rangeToDiff = require('../utils/range-to-diff.js')
@@ -16,6 +18,7 @@ let defaultOptions = {
16
18
  type: 'alphabetical',
17
19
  ignoreCase: true,
18
20
  locales: 'en-US',
21
+ alphabet: '',
19
22
  order: 'asc',
20
23
  }
21
24
  const sortSwitchCase = createEslintRule.createEslintRule({
@@ -30,6 +33,7 @@ const sortSwitchCase = createEslintRule.createEslintRule({
30
33
  settings,
31
34
  defaultOptions,
32
35
  )
36
+ validateCustomSortConfiguration.validateCustomSortConfiguration(options)
33
37
  let sourceCode = getSourceCode.getSourceCode(context)
34
38
  let isDiscriminantTrue =
35
39
  switchNode.discriminant.type === 'Literal' &&
@@ -67,10 +71,13 @@ const sortSwitchCase = createEslintRule.createEslintRule({
67
71
  (hasUnsortedNodes = sortedCaseNameSortingNodes.some(
68
72
  (node, index) => node !== caseNodesSortingNodeGroup[index],
69
73
  ))
74
+ let nodeIndexMap = createNodeIndexMap.createNodeIndexMap(
75
+ sortedCaseNameSortingNodes,
76
+ )
70
77
  pairwise.pairwise(caseNodesSortingNodeGroup, (left, right) => {
71
- let indexOfLeft = sortedCaseNameSortingNodes.indexOf(left)
72
- let indexOfRight = sortedCaseNameSortingNodes.indexOf(right)
73
- if (indexOfLeft < indexOfRight) {
78
+ let leftIndex = nodeIndexMap.get(left)
79
+ let rightIndex = nodeIndexMap.get(right)
80
+ if (leftIndex < rightIndex) {
74
81
  return
75
82
  }
76
83
  context.report({
@@ -218,9 +225,10 @@ const sortSwitchCase = createEslintRule.createEslintRule({
218
225
  properties: {
219
226
  specialCharacters: commonJsonSchemas.specialCharactersJsonSchema,
220
227
  ignoreCase: commonJsonSchemas.ignoreCaseJsonSchema,
228
+ alphabet: commonJsonSchemas.alphabetJsonSchema,
229
+ type: commonJsonSchemas.buildTypeJsonSchema(),
221
230
  locales: commonJsonSchemas.localesJsonSchema,
222
231
  order: commonJsonSchemas.orderJsonSchema,
223
- type: commonJsonSchemas.typeJsonSchema,
224
232
  },
225
233
  additionalProperties: false,
226
234
  type: 'object',
@@ -263,18 +271,12 @@ let reduceCaseSortingNodes = (caseNodes, endsBlock) =>
263
271
  )
264
272
  let caseHasBreakOrReturn = caseNode => {
265
273
  var _a
266
- if (caseNode.consequent.length === 0) {
267
- return false
268
- }
269
- if (
274
+ let statements =
270
275
  ((_a = caseNode.consequent[0]) == null ? void 0 : _a.type) ===
271
276
  'BlockStatement'
272
- ) {
273
- return caseNode.consequent[0].body.some(statementIsBreakOrReturn)
274
- }
275
- return caseNode.consequent.some(currentConsequent =>
276
- statementIsBreakOrReturn(currentConsequent),
277
- )
277
+ ? caseNode.consequent[0].body
278
+ : caseNode.consequent
279
+ return statements.some(statementIsBreakOrReturn)
278
280
  }
279
281
  let statementIsBreakOrReturn = statement =>
280
282
  statement.type === 'BreakStatement' || statement.type === 'ReturnStatement'
@@ -1,9 +1,11 @@
1
1
  'use strict'
2
2
  const commonJsonSchemas = require('../utils/common-json-schemas.js')
3
3
  const validateNewlinesAndPartitionConfiguration = require('../utils/validate-newlines-and-partition-configuration.js')
4
+ const validateCustomSortConfiguration = require('../utils/validate-custom-sort-configuration.js')
4
5
  const validateGroupsConfiguration = require('../utils/validate-groups-configuration.js')
5
6
  const getEslintDisabledLines = require('../utils/get-eslint-disabled-lines.js')
6
7
  const isNodeEslintDisabled = require('../utils/is-node-eslint-disabled.js')
8
+ const createNodeIndexMap = require('../utils/create-node-index-map.js')
7
9
  const isPartitionComment = require('../utils/is-partition-comment.js')
8
10
  const sortNodesByGroups = require('../utils/sort-nodes-by-groups.js')
9
11
  const getCommentsBefore = require('../utils/get-comments-before.js')
@@ -28,6 +30,7 @@ let defaultOptions = {
28
30
  type: 'alphabetical',
29
31
  ignoreCase: true,
30
32
  locales: 'en-US',
33
+ alphabet: '',
31
34
  order: 'asc',
32
35
  groups: [],
33
36
  }
@@ -40,6 +43,7 @@ const sortUnionTypes = createEslintRule.createEslintRule({
40
43
  settings,
41
44
  defaultOptions,
42
45
  )
46
+ validateCustomSortConfiguration.validateCustomSortConfiguration(options)
43
47
  validateGroupsConfiguration.validateGroupsConfiguration(
44
48
  options.groups,
45
49
  [
@@ -69,7 +73,6 @@ const sortUnionTypes = createEslintRule.createEslintRule({
69
73
  })
70
74
  let formattedMembers = node.types.reduce(
71
75
  (accumulator, type) => {
72
- var _a, _b
73
76
  let { defineGroup, getGroup } = useGroups.useGroups(options)
74
77
  switch (type.type) {
75
78
  case 'TSTemplateLiteralType':
@@ -128,8 +131,8 @@ const sortUnionTypes = createEslintRule.createEslintRule({
128
131
  defineGroup('union')
129
132
  break
130
133
  }
131
- let lastSortingNode =
132
- (_a = accumulator.at(-1)) == null ? void 0 : _a.at(-1)
134
+ let lastGroup = accumulator.at(-1)
135
+ let lastSortingNode = lastGroup == null ? void 0 : lastGroup.at(-1)
133
136
  let sortingNode = {
134
137
  isEslintDisabled: isNodeEslintDisabled.isNodeEslintDisabled(
135
138
  type,
@@ -141,15 +144,14 @@ const sortUnionTypes = createEslintRule.createEslintRule({
141
144
  node: type,
142
145
  }
143
146
  if (
144
- (options.partitionByComment &&
145
- isPartitionComment.hasPartitionComment(
146
- options.partitionByComment,
147
- getCommentsBefore.getCommentsBefore({
148
- tokenValueToIgnoreBefore: '|',
149
- node: type,
150
- sourceCode,
151
- }),
152
- )) ||
147
+ isPartitionComment.hasPartitionComment(
148
+ options.partitionByComment,
149
+ getCommentsBefore.getCommentsBefore({
150
+ tokenValueToIgnoreBefore: '|',
151
+ node: type,
152
+ sourceCode,
153
+ }),
154
+ ) ||
153
155
  (options.partitionByNewLine &&
154
156
  lastSortingNode &&
155
157
  getLinesBetween.getLinesBetween(
@@ -158,9 +160,10 @@ const sortUnionTypes = createEslintRule.createEslintRule({
158
160
  sortingNode,
159
161
  ))
160
162
  ) {
161
- accumulator.push([])
163
+ lastGroup = []
164
+ accumulator.push(lastGroup)
162
165
  }
163
- ;(_b = accumulator.at(-1)) == null ? void 0 : _b.push(sortingNode)
166
+ lastGroup == null ? void 0 : lastGroup.push(sortingNode)
164
167
  return accumulator
165
168
  },
166
169
  [[]],
@@ -173,17 +176,18 @@ const sortUnionTypes = createEslintRule.createEslintRule({
173
176
  let sortedNodes = sortNodesExcludingEslintDisabled(false)
174
177
  let sortedNodesExcludingEslintDisabled =
175
178
  sortNodesExcludingEslintDisabled(true)
179
+ let nodeIndexMap = createNodeIndexMap.createNodeIndexMap(sortedNodes)
176
180
  pairwise.pairwise(nodes, (left, right) => {
177
181
  let leftNumber = getGroupNumber.getGroupNumber(options.groups, left)
178
182
  let rightNumber = getGroupNumber.getGroupNumber(options.groups, right)
179
- let indexOfLeft = sortedNodes.indexOf(left)
180
- let indexOfRight = sortedNodes.indexOf(right)
183
+ let leftIndex = nodeIndexMap.get(left)
184
+ let rightIndex = nodeIndexMap.get(right)
181
185
  let indexOfRightExcludingEslintDisabled =
182
186
  sortedNodesExcludingEslintDisabled.indexOf(right)
183
187
  let messageIds = []
184
188
  if (
185
- indexOfLeft > indexOfRight ||
186
- indexOfLeft >= indexOfRightExcludingEslintDisabled
189
+ leftIndex > rightIndex ||
190
+ leftIndex >= indexOfRightExcludingEslintDisabled
187
191
  ) {
188
192
  messageIds.push(
189
193
  leftNumber === rightNumber
@@ -249,10 +253,11 @@ const sortUnionTypes = createEslintRule.createEslintRule({
249
253
  specialCharacters: commonJsonSchemas.specialCharactersJsonSchema,
250
254
  newlinesBetween: commonJsonSchemas.newlinesBetweenJsonSchema,
251
255
  ignoreCase: commonJsonSchemas.ignoreCaseJsonSchema,
256
+ alphabet: commonJsonSchemas.alphabetJsonSchema,
257
+ type: commonJsonSchemas.buildTypeJsonSchema(),
252
258
  locales: commonJsonSchemas.localesJsonSchema,
253
259
  groups: commonJsonSchemas.groupsJsonSchema,
254
260
  order: commonJsonSchemas.orderJsonSchema,
255
- type: commonJsonSchemas.typeJsonSchema,
256
261
  },
257
262
  additionalProperties: false,
258
263
  type: 'object',
@@ -1,8 +1,10 @@
1
1
  'use strict'
2
2
  const commonJsonSchemas = require('../utils/common-json-schemas.js')
3
3
  const sortNodesByDependencies = require('../utils/sort-nodes-by-dependencies.js')
4
+ const validateCustomSortConfiguration = require('../utils/validate-custom-sort-configuration.js')
4
5
  const getEslintDisabledLines = require('../utils/get-eslint-disabled-lines.js')
5
6
  const isNodeEslintDisabled = require('../utils/is-node-eslint-disabled.js')
7
+ const createNodeIndexMap = require('../utils/create-node-index-map.js')
6
8
  const isPartitionComment = require('../utils/is-partition-comment.js')
7
9
  const getCommentsBefore = require('../utils/get-comments-before.js')
8
10
  const createEslintRule = require('../utils/create-eslint-rule.js')
@@ -23,6 +25,7 @@ let defaultOptions = {
23
25
  type: 'alphabetical',
24
26
  ignoreCase: true,
25
27
  locales: 'en-US',
28
+ alphabet: '',
26
29
  order: 'asc',
27
30
  }
28
31
  const sortVariableDeclarations = createEslintRule.createEslintRule({
@@ -37,6 +40,7 @@ const sortVariableDeclarations = createEslintRule.createEslintRule({
37
40
  settings,
38
41
  defaultOptions,
39
42
  )
43
+ validateCustomSortConfiguration.validateCustomSortConfiguration(options)
40
44
  let sourceCode = getSourceCode.getSourceCode(context)
41
45
  let eslintDisabledLines = getEslintDisabledLines.getEslintDisabledLines({
42
46
  ruleName: context.id,
@@ -143,14 +147,13 @@ const sortVariableDeclarations = createEslintRule.createEslintRule({
143
147
  name,
144
148
  }
145
149
  if (
146
- (options.partitionByComment &&
147
- isPartitionComment.hasPartitionComment(
148
- options.partitionByComment,
149
- getCommentsBefore.getCommentsBefore({
150
- node: declaration,
151
- sourceCode,
152
- }),
153
- )) ||
150
+ isPartitionComment.hasPartitionComment(
151
+ options.partitionByComment,
152
+ getCommentsBefore.getCommentsBefore({
153
+ node: declaration,
154
+ sourceCode,
155
+ }),
156
+ ) ||
154
157
  (options.partitionByNewLine &&
155
158
  lastSortingNode &&
156
159
  getLinesBetween.getLinesBetween(
@@ -181,14 +184,15 @@ const sortVariableDeclarations = createEslintRule.createEslintRule({
181
184
  let sortedNodesExcludingEslintDisabled =
182
185
  sortNodesIgnoringEslintDisabledNodes(true)
183
186
  let nodes = formattedMembers.flat()
187
+ let nodeIndexMap = createNodeIndexMap.createNodeIndexMap(sortedNodes)
184
188
  pairwise.pairwise(nodes, (left, right) => {
185
- let indexOfLeft = sortedNodes.indexOf(left)
186
- let indexOfRight = sortedNodes.indexOf(right)
189
+ let leftIndex = nodeIndexMap.get(left)
190
+ let rightIndex = nodeIndexMap.get(right)
187
191
  let indexOfRightExcludingEslintDisabled =
188
192
  sortedNodesExcludingEslintDisabled.indexOf(right)
189
193
  if (
190
- indexOfLeft < indexOfRight &&
191
- indexOfLeft < indexOfRightExcludingEslintDisabled
194
+ leftIndex < rightIndex &&
195
+ leftIndex < indexOfRightExcludingEslintDisabled
192
196
  ) {
193
197
  return
194
198
  }
@@ -231,9 +235,10 @@ const sortVariableDeclarations = createEslintRule.createEslintRule({
231
235
  partitionByNewLine: commonJsonSchemas.partitionByNewLineJsonSchema,
232
236
  specialCharacters: commonJsonSchemas.specialCharactersJsonSchema,
233
237
  ignoreCase: commonJsonSchemas.ignoreCaseJsonSchema,
238
+ alphabet: commonJsonSchemas.alphabetJsonSchema,
239
+ type: commonJsonSchemas.buildTypeJsonSchema(),
234
240
  locales: commonJsonSchemas.localesJsonSchema,
235
241
  order: commonJsonSchemas.orderJsonSchema,
236
- type: commonJsonSchemas.typeJsonSchema,
237
242
  },
238
243
  additionalProperties: false,
239
244
  type: 'object',
@@ -0,0 +1,385 @@
1
+ 'use strict'
2
+ var __defProp = Object.defineProperty
3
+ var __defNormalProp = (obj, key, value) =>
4
+ key in obj
5
+ ? __defProp(obj, key, {
6
+ enumerable: true,
7
+ configurable: true,
8
+ writable: true,
9
+ value,
10
+ })
11
+ : (obj[key] = value)
12
+ var __publicField = (obj, key, value) =>
13
+ __defNormalProp(obj, typeof key !== 'symbol' ? key + '' : key, value)
14
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
15
+ const naturalOrderby = require('natural-orderby')
16
+ const convertBooleanToSign = require('./convert-boolean-to-sign.js')
17
+ class Alphabet {
18
+ constructor(characters) {
19
+ __publicField(this, '_characters', [])
20
+ this._characters = characters
21
+ }
22
+ /**
23
+ * Generates an alphabet from the given characters.
24
+ * @param {string|string[]} values - The characters to generate the alphabet from
25
+ * @returns {Alphabet} - The wrapped alphabet
26
+ */
27
+ static generateFrom(values) {
28
+ let arrayValues = typeof values === 'string' ? [...values] : values
29
+ if (arrayValues.length !== new Set(arrayValues).size) {
30
+ throw new Error('The alphabet must not contain repeated characters')
31
+ }
32
+ if (arrayValues.some(value => value.length !== 1)) {
33
+ throw new Error('The alphabet must contain single characters')
34
+ }
35
+ return new Alphabet(
36
+ arrayValues.map(value =>
37
+ Alphabet._convertCodepointToCharacter(value.codePointAt(0)),
38
+ ),
39
+ )
40
+ }
41
+ /**
42
+ * Generates an alphabet containing relevant characters from the Unicode
43
+ * standard. Contains the Unicode planes 0 and 1.
44
+ * @returns {Alphabet} - The generated alphabet
45
+ */
46
+ static generateRecommendedAlphabet() {
47
+ return Alphabet._generateAlphabetToRange(131071 + 1)
48
+ }
49
+ /**
50
+ * Generates an alphabet containing all characters from the Unicode standard
51
+ * except for irrelevant Unicode planes.
52
+ * Contains the Unicode planes 0, 1, 2 and 3.
53
+ * @returns {Alphabet} - The generated alphabet
54
+ */
55
+ static generateCompleteAlphabet() {
56
+ return Alphabet._generateAlphabetToRange(262143 + 1)
57
+ }
58
+ static _convertCodepointToCharacter(codePoint) {
59
+ let character = String.fromCodePoint(codePoint)
60
+ let lowercaseCharacter = character.toLowerCase()
61
+ let uppercaseCharacter = character.toUpperCase()
62
+ return {
63
+ value: character,
64
+ codePoint,
65
+ ...(lowercaseCharacter === character
66
+ ? null
67
+ : {
68
+ lowercaseCharacterCodePoint: lowercaseCharacter.codePointAt(0),
69
+ }),
70
+ ...(uppercaseCharacter === character
71
+ ? null
72
+ : {
73
+ uppercaseCharacterCodePoint: uppercaseCharacter.codePointAt(0),
74
+ }),
75
+ }
76
+ }
77
+ /**
78
+ * Generates an alphabet containing relevant characters from the Unicode
79
+ * standard.
80
+ * @param {number} maxCodePoint - The maximum code point to generate the
81
+ * alphabet to
82
+ * @returns {Alphabet} - The generated alphabet
83
+ */
84
+ static _generateAlphabetToRange(maxCodePoint) {
85
+ let totalCharacters = Array.from({ length: maxCodePoint }, (_, i) =>
86
+ Alphabet._convertCodepointToCharacter(i),
87
+ )
88
+ return new Alphabet(totalCharacters)
89
+ }
90
+ /**
91
+ * For each character with a lower and upper case, permutes the two cases
92
+ * so that the alphabet is ordered by the case priority entered.
93
+ * @param {string} casePriority - The case to prioritize
94
+ * @returns {Alphabet} - The same alphabet instance with the cases prioritized
95
+ * @example
96
+ * Alphabet.generateFrom('aAbBcdCD')
97
+ * .prioritizeCase('uppercase')
98
+ * // Returns 'AaBbCDcd'
99
+ */
100
+ prioritizeCase(casePriority) {
101
+ let charactersWithCase = this._getCharactersWithCase()
102
+ let parsedIndexes = /* @__PURE__ */ new Set()
103
+ let indexByCodePoints = this._characters.reduce(
104
+ (indexByCodePoint, character, index) => {
105
+ indexByCodePoint[character.codePoint] = index
106
+ return indexByCodePoint
107
+ },
108
+ {},
109
+ )
110
+ for (let { character, index } of charactersWithCase) {
111
+ if (parsedIndexes.has(index)) {
112
+ continue
113
+ }
114
+ parsedIndexes.add(index)
115
+ let otherCharacterIndex =
116
+ indexByCodePoints[
117
+ character.uppercaseCharacterCodePoint ??
118
+ character.lowercaseCharacterCodePoint
119
+ ]
120
+ if (otherCharacterIndex === void 0) {
121
+ continue
122
+ }
123
+ parsedIndexes.add(otherCharacterIndex)
124
+ let isCharacterUppercase = !character.uppercaseCharacterCodePoint
125
+ if (isCharacterUppercase) {
126
+ if (
127
+ (casePriority === 'uppercase' && index < otherCharacterIndex) ||
128
+ (casePriority === 'lowercase' && index > otherCharacterIndex)
129
+ ) {
130
+ continue
131
+ }
132
+ } else {
133
+ if (
134
+ (casePriority === 'uppercase' && index > otherCharacterIndex) ||
135
+ (casePriority === 'lowercase' && index < otherCharacterIndex)
136
+ ) {
137
+ continue
138
+ }
139
+ }
140
+ this._characters[index] = this._characters[otherCharacterIndex]
141
+ this._characters[otherCharacterIndex] = character
142
+ }
143
+ return this
144
+ }
145
+ /**
146
+ * Adds specific characters to the end of the alphabet.
147
+ * @param {string|string[]} values - The characters to push to the alphabet
148
+ * @returns {Alphabet} - The same alphabet instance without the specified
149
+ * characters
150
+ * @example
151
+ * Alphabet.generateFrom('ab')
152
+ * .pushCharacters('cd')
153
+ * // Returns 'abcd'
154
+ */
155
+ pushCharacters(values) {
156
+ let arrayValues = typeof values === 'string' ? [...values] : values
157
+ let valuesSet = new Set(arrayValues)
158
+ let valuesAlreadyExisting = this._characters.filter(({ value }) =>
159
+ valuesSet.has(value),
160
+ )
161
+ if (valuesAlreadyExisting.length > 0) {
162
+ throw new Error(
163
+ `The alphabet already contains the characters ${valuesAlreadyExisting
164
+ .slice(0, 5)
165
+ .map(({ value }) => value)
166
+ .join(', ')}`,
167
+ )
168
+ }
169
+ if (arrayValues.some(value => value.length !== 1)) {
170
+ throw new Error('Only single characters may be pushed')
171
+ }
172
+ this._characters.push(
173
+ ...[...valuesSet].map(value =>
174
+ Alphabet._convertCodepointToCharacter(value.codePointAt(0)),
175
+ ),
176
+ )
177
+ return this
178
+ }
179
+ /**
180
+ * Permutes characters with cases so that all characters with the entered case
181
+ * are put before the other characters.
182
+ * @param {string} caseToComeFirst - The case to put before the other
183
+ * characters
184
+ * @returns {Alphabet} - The same alphabet instance with all characters with
185
+ * case before all the characters with the other case
186
+ */
187
+ placeAllWithCaseBeforeAllWithOtherCase(caseToComeFirst) {
188
+ let charactersWithCase = this._getCharactersWithCase()
189
+ let orderedCharacters = [
190
+ ...charactersWithCase.filter(character =>
191
+ caseToComeFirst === 'uppercase'
192
+ ? !character.character.uppercaseCharacterCodePoint
193
+ : character.character.uppercaseCharacterCodePoint,
194
+ ),
195
+ ...charactersWithCase.filter(character =>
196
+ caseToComeFirst === 'uppercase'
197
+ ? character.character.uppercaseCharacterCodePoint
198
+ : !character.character.uppercaseCharacterCodePoint,
199
+ ),
200
+ ]
201
+ for (let [i, element] of charactersWithCase.entries()) {
202
+ this._characters[element.index] = orderedCharacters[i].character
203
+ }
204
+ return this
205
+ }
206
+ /**
207
+ * Places a specific character right before another character in the alphabet.
208
+ * @param {object} params - The parameters for the operation
209
+ * @param {string} params.characterBefore - The character to come before
210
+ * characterAfter
211
+ * @param {string} params.characterAfter - The target character
212
+ * @returns {Alphabet} - The same alphabet instance with the specific
213
+ * character prioritized
214
+ * @example
215
+ * Alphabet.generateFrom('ab-cd/')
216
+ * .placeCharacterBefore({ characterBefore: '/', characterAfter: '-' })
217
+ * // Returns 'ab/-cd'
218
+ */
219
+ placeCharacterBefore({ characterBefore, characterAfter }) {
220
+ return this._placeCharacterBeforeOrAfter({
221
+ characterBefore,
222
+ characterAfter,
223
+ type: 'before',
224
+ })
225
+ }
226
+ /**
227
+ * Places a specific character right after another character in the alphabet.
228
+ * @param {object} params - The parameters for the operation
229
+ * @param {string} params.characterBefore - The target character
230
+ * @param {string} params.characterAfter - The character to come after
231
+ * characterBefore
232
+ * @returns {Alphabet} - The same alphabet instance with the specific
233
+ * character prioritized
234
+ * @example
235
+ * Alphabet.generateFrom('ab-cd/')
236
+ * .placeCharacterAfter({ characterBefore: '/', characterAfter: '-' })
237
+ * // Returns 'abcd/-'
238
+ */
239
+ placeCharacterAfter({ characterBefore, characterAfter }) {
240
+ return this._placeCharacterBeforeOrAfter({
241
+ characterBefore,
242
+ characterAfter,
243
+ type: 'after',
244
+ })
245
+ }
246
+ /**
247
+ * Removes specific characters from the alphabet by their range.
248
+ * @param {object} range - The Unicode range to remove characters from
249
+ * @param {number} range.start - The starting Unicode codepoint
250
+ * @param {number} range.end - The ending Unicode codepoint
251
+ * @returns {Alphabet} - The same alphabet instance without the characters
252
+ * from the specified range
253
+ */
254
+ removeUnicodeRange({ start, end }) {
255
+ this._characters = this._characters.filter(
256
+ ({ codePoint }) => codePoint < start || codePoint > end,
257
+ )
258
+ return this
259
+ }
260
+ /**
261
+ * Sorts the alphabet by the sorting function provided.
262
+ * @param {Function} sortingFunction - The sorting function to use
263
+ * @returns {Alphabet} - The same alphabet instance sorted by the sorting function provided
264
+ */
265
+ sortBy(sortingFunction) {
266
+ this._characters.sort((a, b) => sortingFunction(a.value, b.value))
267
+ return this
268
+ }
269
+ /**
270
+ * Removes specific characters from the alphabet.
271
+ * @param {string|string[]} values - The characters to remove from the
272
+ * alphabet
273
+ * @returns {Alphabet} - The same alphabet instance without the specified
274
+ * characters
275
+ * @example
276
+ * Alphabet.generateFrom('abcd')
277
+ * .removeCharacters('dcc')
278
+ * // Returns 'ab'
279
+ */
280
+ removeCharacters(values) {
281
+ this._characters = this._characters.filter(
282
+ ({ value }) => !values.includes(value),
283
+ )
284
+ return this
285
+ }
286
+ /**
287
+ * Sorts the alphabet by the natural order of the characters using
288
+ * `natural-orderby`.
289
+ * @param {string} locale - The locale to use for sorting
290
+ * @returns {Alphabet} - The same alphabet instance sorted by the natural
291
+ * order of the characters
292
+ */
293
+ sortByNaturalSort(locale) {
294
+ let naturalCompare = naturalOrderby.compare({
295
+ locale,
296
+ })
297
+ return this.sortBy((a, b) => naturalCompare(a, b))
298
+ }
299
+ /**
300
+ * Sorts the alphabet by the character code point.
301
+ * @returns {Alphabet} - The same alphabet instance sorted by the character
302
+ * code point
303
+ */
304
+ sortByCharCodeAt() {
305
+ return this.sortBy((a, b) =>
306
+ convertBooleanToSign.convertBooleanToSign(
307
+ a.charCodeAt(0) > b.charCodeAt(0),
308
+ ),
309
+ )
310
+ }
311
+ /**
312
+ * Sorts the alphabet by the locale order of the characters.
313
+ * @param {Intl.LocalesArgument} locales - The locales to use for sorting
314
+ * @returns {Alphabet} - The same alphabet instance sorted by the locale
315
+ * order of the characters
316
+ */
317
+ sortByLocaleCompare(locales) {
318
+ return this.sortBy((a, b) => a.localeCompare(b, locales))
319
+ }
320
+ /**
321
+ * Retrieves the characters from the alphabet.
322
+ * @returns {string} The characters from the alphabet
323
+ */
324
+ getCharacters() {
325
+ return this._characters.map(({ value }) => value).join('')
326
+ }
327
+ /**
328
+ * Reverses the alphabet.
329
+ * @returns {Alphabet} - The same alphabet instance reversed
330
+ * @example
331
+ * Alphabet.generateFrom('ab')
332
+ * .reverse()
333
+ * // Returns 'ba'
334
+ */
335
+ reverse() {
336
+ this._characters.reverse()
337
+ return this
338
+ }
339
+ _placeCharacterBeforeOrAfter({ characterBefore, characterAfter, type }) {
340
+ let indexOfCharacterAfter = this._characters.findIndex(
341
+ ({ value }) => value === characterAfter,
342
+ )
343
+ let indexOfCharacterBefore = this._characters.findIndex(
344
+ ({ value }) => value === characterBefore,
345
+ )
346
+ if (indexOfCharacterAfter === -1) {
347
+ throw new Error(`Character ${characterAfter} not found in alphabet`)
348
+ }
349
+ if (indexOfCharacterBefore === -1) {
350
+ throw new Error(`Character ${characterBefore} not found in alphabet`)
351
+ }
352
+ if (indexOfCharacterBefore <= indexOfCharacterAfter) {
353
+ return this
354
+ }
355
+ this._characters.splice(
356
+ type === 'before' ? indexOfCharacterAfter : indexOfCharacterBefore + 1,
357
+ 0,
358
+ this._characters[
359
+ type === 'before' ? indexOfCharacterBefore : indexOfCharacterAfter
360
+ ],
361
+ )
362
+ this._characters.splice(
363
+ type === 'before' ? indexOfCharacterBefore + 1 : indexOfCharacterAfter,
364
+ 1,
365
+ )
366
+ return this
367
+ }
368
+ _getCharactersWithCase() {
369
+ return this._characters
370
+ .map((character, index) => {
371
+ if (
372
+ !character.uppercaseCharacterCodePoint &&
373
+ !character.lowercaseCharacterCodePoint
374
+ ) {
375
+ return null
376
+ }
377
+ return {
378
+ character,
379
+ index,
380
+ }
381
+ })
382
+ .filter(element => element !== null)
383
+ }
384
+ }
385
+ exports.Alphabet = Alphabet