@@ -4,44 +4,61 @@ import type { SortingNode } from '../types/sorting-node'
44import type { Options } from './sort-maps/types'
55
66import {
7+ buildCustomGroupsArrayJsonSchema ,
78 partitionByCommentJsonSchema ,
89 partitionByNewLineJsonSchema ,
910 specialCharactersJsonSchema ,
11+ newlinesBetweenJsonSchema ,
1012 ignoreCaseJsonSchema ,
1113 buildTypeJsonSchema ,
1214 alphabetJsonSchema ,
1315 localesJsonSchema ,
16+ groupsJsonSchema ,
1417 orderJsonSchema ,
1518} from '../utils/common-json-schemas'
19+ import { validateGeneratedGroupsConfiguration } from '../utils/validate-generated-groups-configuration'
1620import { validateCustomSortConfiguration } from '../utils/validate-custom-sort-configuration'
21+ import { getCustomGroupsCompareOptions } from '../utils/get-custom-groups-compare-options'
1722import { getEslintDisabledLines } from '../utils/get-eslint-disabled-lines'
23+ import { doesCustomGroupMatch } from './sort-maps/does-custom-group-match'
1824import { isNodeEslintDisabled } from '../utils/is-node-eslint-disabled'
1925import { hasPartitionComment } from '../utils/has-partition-comment'
2026import { createNodeIndexMap } from '../utils/create-node-index-map'
27+ import { sortNodesByGroups } from '../utils/sort-nodes-by-groups'
2128import { getCommentsBefore } from '../utils/get-comments-before'
29+ import { getNewlinesErrors } from '../utils/get-newlines-errors'
30+ import { singleCustomGroupJsonSchema } from './sort-maps/types'
2231import { createEslintRule } from '../utils/create-eslint-rule'
2332import { getLinesBetween } from '../utils/get-lines-between'
33+ import { getGroupNumber } from '../utils/get-group-number'
2434import { getSourceCode } from '../utils/get-source-code'
2535import { toSingleLine } from '../utils/to-single-line'
2636import { rangeToDiff } from '../utils/range-to-diff'
2737import { getSettings } from '../utils/get-settings'
2838import { isSortable } from '../utils/is-sortable'
2939import { makeFixes } from '../utils/make-fixes'
30- import { sortNodes } from '../utils/sort-nodes '
40+ import { useGroups } from '../utils/use-groups '
3141import { complete } from '../utils/complete'
3242import { pairwise } from '../utils/pairwise'
3343
34- type MESSAGE_ID = 'unexpectedMapElementsOrder'
44+ type MESSAGE_ID =
45+ | 'missedSpacingBetweenMapElementsMembers'
46+ | 'extraSpacingBetweenMapElementsMembers'
47+ | 'unexpectedMapElementsGroupOrder'
48+ | 'unexpectedMapElementsOrder'
3549
3650let defaultOptions : Required < Options [ 0 ] > = {
3751 specialCharacters : 'keep' ,
3852 partitionByComment : false ,
3953 partitionByNewLine : false ,
54+ newlinesBetween : 'ignore' ,
4055 type : 'alphabetical' ,
4156 ignoreCase : true ,
57+ customGroups : [ ] ,
4258 locales : 'en-US' ,
4359 alphabet : '' ,
4460 order : 'asc' ,
61+ groups : [ ] ,
4562}
4663
4764export default createEslintRule < Options , MESSAGE_ID > ( {
@@ -63,6 +80,12 @@ export default createEslintRule<Options, MESSAGE_ID>({
6380 let settings = getSettings ( context . settings )
6481 let options = complete ( context . options . at ( 0 ) , settings , defaultOptions )
6582 validateCustomSortConfiguration ( options )
83+ validateGeneratedGroupsConfiguration ( {
84+ customGroups : options . customGroups ,
85+ groups : options . groups ,
86+ selectors : [ ] ,
87+ modifiers : [ ] ,
88+ } )
6689
6790 let sourceCode = getSourceCode ( context )
6891 let eslintDisabledLines = getEslintDisabledLines ( {
@@ -104,12 +127,33 @@ export default createEslintRule<Options, MESSAGE_ID>({
104127 }
105128
106129 let lastSortingNode = formattedMembers . at ( - 1 ) ?. at ( - 1 )
130+
131+ let { defineGroup, getGroup } = useGroups ( options )
132+ for ( let customGroup of options . customGroups ) {
133+ if (
134+ doesCustomGroupMatch ( {
135+ elementName : name ,
136+ customGroup,
137+ } )
138+ ) {
139+ defineGroup ( customGroup . groupName , true )
140+ /**
141+ * If the custom group is not referenced in the `groups` option, it
142+ * will be ignored
143+ */
144+ if ( getGroup ( ) === customGroup . groupName ) {
145+ break
146+ }
147+ }
148+ }
149+
107150 let sortingNode : SortingNode = {
108151 isEslintDisabled : isNodeEslintDisabled (
109152 element ,
110153 eslintDisabledLines ,
111154 ) ,
112155 size : rangeToDiff ( element , sourceCode ) ,
156+ group : getGroup ( ) ,
113157 node : element ,
114158 name,
115159 }
@@ -136,7 +180,11 @@ export default createEslintRule<Options, MESSAGE_ID>({
136180 let sortNodesExcludingEslintDisabled = (
137181 ignoreEslintDisabledNodes : boolean ,
138182 ) : SortingNode [ ] =>
139- sortNodes ( nodes , options , { ignoreEslintDisabledNodes } )
183+ sortNodesByGroups ( nodes , options , {
184+ getGroupCompareOptions : groupNumber =>
185+ getCustomGroupsCompareOptions ( options , groupNumber ) ,
186+ ignoreEslintDisabledNodes,
187+ } )
140188 let sortedNodes = sortNodesExcludingEslintDisabled ( false )
141189 let sortedNodesExcludingEslintDisabled =
142190 sortNodesExcludingEslintDisabled ( true )
@@ -147,31 +195,59 @@ export default createEslintRule<Options, MESSAGE_ID>({
147195 let leftIndex = nodeIndexMap . get ( left ) !
148196 let rightIndex = nodeIndexMap . get ( right ) !
149197
198+ let leftNumber = getGroupNumber ( options . groups , left )
199+ let rightNumber = getGroupNumber ( options . groups , right )
200+
150201 let indexOfRightExcludingEslintDisabled =
151202 sortedNodesExcludingEslintDisabled . indexOf ( right )
203+
204+ let messageIds : MESSAGE_ID [ ] = [ ]
205+
152206 if (
153- leftIndex < rightIndex &&
154- leftIndex < indexOfRightExcludingEslintDisabled
207+ leftIndex > rightIndex ||
208+ leftIndex >= indexOfRightExcludingEslintDisabled
155209 ) {
156- return
210+ messageIds . push (
211+ leftNumber === rightNumber
212+ ? 'unexpectedMapElementsOrder'
213+ : 'unexpectedMapElementsGroupOrder' ,
214+ )
157215 }
158216
159- context . report ( {
160- fix : fixer =>
161- makeFixes ( {
162- sortedNodes : sortedNodesExcludingEslintDisabled ,
163- sourceCode,
164- options,
165- fixer,
166- nodes,
167- } ) ,
168- data : {
169- right : toSingleLine ( right . name ) ,
170- left : toSingleLine ( left . name ) ,
171- } ,
172- messageId : 'unexpectedMapElementsOrder' ,
173- node : right . node ,
174- } )
217+ messageIds = [
218+ ...messageIds ,
219+ ...getNewlinesErrors ( {
220+ missedSpacingError : 'missedSpacingBetweenMapElementsMembers' ,
221+ extraSpacingError : 'extraSpacingBetweenMapElementsMembers' ,
222+ rightNum : rightNumber ,
223+ leftNum : leftNumber ,
224+ sourceCode,
225+ options,
226+ right,
227+ left,
228+ } ) ,
229+ ]
230+
231+ for ( let messageId of messageIds ) {
232+ context . report ( {
233+ fix : fixer =>
234+ makeFixes ( {
235+ sortedNodes : sortedNodesExcludingEslintDisabled ,
236+ sourceCode,
237+ options,
238+ fixer,
239+ nodes,
240+ } ) ,
241+ data : {
242+ right : toSingleLine ( right . name ) ,
243+ left : toSingleLine ( left . name ) ,
244+ rightGroup : right . group ,
245+ leftGroup : left . group ,
246+ } ,
247+ node : right . node ,
248+ messageId,
249+ } )
250+ }
175251 } )
176252 }
177253 }
@@ -186,27 +262,38 @@ export default createEslintRule<Options, MESSAGE_ID>({
186262 description :
187263 'Allows you to use comments to separate the maps members into logical groups.' ,
188264 } ,
265+ customGroups : buildCustomGroupsArrayJsonSchema ( {
266+ singleCustomGroupJsonSchema,
267+ } ) ,
189268 partitionByNewLine : partitionByNewLineJsonSchema ,
190269 specialCharacters : specialCharactersJsonSchema ,
270+ newlinesBetween : newlinesBetweenJsonSchema ,
191271 ignoreCase : ignoreCaseJsonSchema ,
192272 alphabet : alphabetJsonSchema ,
193273 type : buildTypeJsonSchema ( ) ,
194274 locales : localesJsonSchema ,
275+ groups : groupsJsonSchema ,
195276 order : orderJsonSchema ,
196277 } ,
197278 additionalProperties : false ,
198279 type : 'object' ,
199280 } ,
200281 ] ,
282+ messages : {
283+ unexpectedMapElementsGroupOrder :
284+ 'Expected "{{right}}" ({{rightGroup}}) to come before "{{left}}" ({{leftGroup}}).' ,
285+ missedSpacingBetweenMapElementsMembers :
286+ 'Missed spacing between "{{left}}" and "{{right}}" members.' ,
287+ extraSpacingBetweenMapElementsMembers :
288+ 'Extra spacing between "{{left}}" and "{{right}}" members.' ,
289+ unexpectedMapElementsOrder :
290+ 'Expected "{{right}}" to come before "{{left}}".' ,
291+ } ,
201292 docs : {
202293 url : 'https://perfectionist.dev/rules/sort-maps' ,
203294 description : 'Enforce sorted Map elements.' ,
204295 recommended : true ,
205296 } ,
206- messages : {
207- unexpectedMapElementsOrder :
208- 'Expected "{{right}}" to come before "{{left}}".' ,
209- } ,
210297 type : 'suggestion' ,
211298 fixable : 'code' ,
212299 } ,
0 commit comments