@@ -46,10 +46,27 @@ function isReservedPropName(name, list) {
46
46
return list . indexOf ( name ) >= 0 ;
47
47
}
48
48
49
+ let attributeMap ;
50
+ // attributeMap = [endrange, true||false if comment in between nodes exists, it needs to be sorted to end]
51
+
52
+ function shouldSortToEnd ( node ) {
53
+ const attr = attributeMap . get ( node ) ;
54
+ return ! ! attr && ! ! attr [ 1 ] ;
55
+ }
56
+
49
57
function contextCompare ( a , b , options ) {
50
58
let aProp = propName ( a ) ;
51
59
let bProp = propName ( b ) ;
52
60
61
+ const aSortToEnd = shouldSortToEnd ( a ) ;
62
+ const bSortToEnd = shouldSortToEnd ( b ) ;
63
+ if ( aSortToEnd && ! bSortToEnd ) {
64
+ return 1 ;
65
+ }
66
+ if ( ! aSortToEnd && bSortToEnd ) {
67
+ return - 1 ;
68
+ }
69
+
53
70
if ( options . reservedFirst ) {
54
71
const aIsReserved = isReservedPropName ( aProp , options . reservedList ) ;
55
72
const bIsReserved = isReservedPropName ( bProp , options . reservedList ) ;
@@ -118,32 +135,79 @@ function contextCompare(a, b, options) {
118
135
* Create an array of arrays where each subarray is composed of attributes
119
136
* that are considered sortable.
120
137
* @param {Array<JSXSpreadAttribute|JSXAttribute> } attributes
138
+ * @param {Object } context The context of the rule
121
139
* @return {Array<Array<JSXAttribute>> }
122
140
*/
123
- function getGroupsOfSortableAttributes ( attributes ) {
141
+ function getGroupsOfSortableAttributes ( attributes , context ) {
142
+ const sourceCode = context . getSourceCode ( ) ;
143
+
124
144
const sortableAttributeGroups = [ ] ;
125
145
let groupCount = 0 ;
146
+ function addtoSortableAttributeGroups ( attribute ) {
147
+ sortableAttributeGroups [ groupCount - 1 ] . push ( attribute ) ;
148
+ }
149
+
126
150
for ( let i = 0 ; i < attributes . length ; i ++ ) {
151
+ const attribute = attributes [ i ] ;
152
+ const nextAttribute = attributes [ i + 1 ] ;
153
+ const attributeline = attribute . loc . start . line ;
154
+ let comment = [ ] ;
155
+ try {
156
+ comment = sourceCode . getCommentsAfter ( attribute ) ;
157
+ } catch ( e ) { /**/ }
127
158
const lastAttr = attributes [ i - 1 ] ;
159
+ const attrIsSpread = attribute . type === 'JSXSpreadAttribute' ;
160
+
128
161
// If we have no groups or if the last attribute was JSXSpreadAttribute
129
162
// then we start a new group. Append attributes to the group until we
130
163
// come across another JSXSpreadAttribute or exhaust the array.
131
164
if (
132
165
! lastAttr
133
- || ( lastAttr . type === 'JSXSpreadAttribute'
134
- && attributes [ i ] . type !== 'JSXSpreadAttribute' )
166
+ || ( lastAttr . type === 'JSXSpreadAttribute' && ! attrIsSpread )
135
167
) {
136
168
groupCount += 1 ;
137
169
sortableAttributeGroups [ groupCount - 1 ] = [ ] ;
138
170
}
139
- if ( attributes [ i ] . type !== 'JSXSpreadAttribute' ) {
140
- sortableAttributeGroups [ groupCount - 1 ] . push ( attributes [ i ] ) ;
171
+ if ( ! attrIsSpread ) {
172
+ if ( comment . length === 0 ) {
173
+ attributeMap . set ( attribute , [ attribute . range [ 1 ] , false ] ) ;
174
+ addtoSortableAttributeGroups ( attribute ) ;
175
+ } else {
176
+ const firstComment = comment [ 0 ] ;
177
+ const commentline = firstComment . loc . start . line ;
178
+ if ( comment . length === 1 ) {
179
+ if ( attributeline + 1 === commentline && nextAttribute ) {
180
+ attributeMap . set ( attribute , [ nextAttribute . range [ 1 ] , true ] ) ;
181
+ addtoSortableAttributeGroups ( attribute ) ;
182
+ i += 1 ;
183
+ } else if ( attributeline === commentline ) {
184
+ if ( firstComment . type === 'Block' ) {
185
+ attributeMap . set ( attribute , [ nextAttribute . range [ 1 ] , true ] ) ;
186
+ i += 1 ;
187
+ } else {
188
+ attributeMap . set ( attribute , [ firstComment . range [ 1 ] , false ] ) ;
189
+ }
190
+ addtoSortableAttributeGroups ( attribute ) ;
191
+ }
192
+ } else if ( comment . length > 1 && attributeline + 1 === comment [ 1 ] . loc . start . line && nextAttribute ) {
193
+ const commentNextAttribute = sourceCode . getCommentsAfter ( nextAttribute ) ;
194
+ attributeMap . set ( attribute , [ nextAttribute . range [ 1 ] , true ] ) ;
195
+ if (
196
+ commentNextAttribute . length === 1
197
+ && nextAttribute . loc . start . line === commentNextAttribute [ 0 ] . loc . start . line
198
+ ) {
199
+ attributeMap . set ( attribute , [ commentNextAttribute [ 0 ] . range [ 1 ] , true ] ) ;
200
+ }
201
+ addtoSortableAttributeGroups ( attribute ) ;
202
+ i += 1 ;
203
+ }
204
+ }
141
205
}
142
206
}
143
207
return sortableAttributeGroups ;
144
208
}
145
209
146
- const generateFixerFunction = ( node , context , reservedList ) => {
210
+ function generateFixerFunction ( node , context , reservedList ) {
147
211
const sourceCode = context . getSourceCode ( ) ;
148
212
const attributes = node . attributes . slice ( 0 ) ;
149
213
const configuration = context . options [ 0 ] || { } ;
@@ -170,7 +234,7 @@ const generateFixerFunction = (node, context, reservedList) => {
170
234
reservedList,
171
235
locale,
172
236
} ;
173
- const sortableAttributeGroups = getGroupsOfSortableAttributes ( attributes ) ;
237
+ const sortableAttributeGroups = getGroupsOfSortableAttributes ( attributes , context ) ;
174
238
const sortedAttributeGroups = sortableAttributeGroups
175
239
. slice ( 0 )
176
240
. map ( ( group ) => group . slice ( 0 ) . sort ( ( a , b ) => contextCompare ( a , b , options ) ) ) ;
@@ -179,13 +243,13 @@ const generateFixerFunction = (node, context, reservedList) => {
179
243
const fixers = [ ] ;
180
244
let source = sourceCode . getText ( ) ;
181
245
182
- // Replace each unsorted attribute with the sorted one.
183
246
sortableAttributeGroups . forEach ( ( sortableGroup , ii ) => {
184
247
sortableGroup . forEach ( ( attr , jj ) => {
185
248
const sortedAttr = sortedAttributeGroups [ ii ] [ jj ] ;
186
- const sortedAttrText = sourceCode . getText ( sortedAttr ) ;
249
+ const sortedAttrText = source . substring ( sortedAttr . range [ 0 ] , attributeMap . get ( sortedAttr ) [ 0 ] ) ;
250
+ const attrrangeEnd = attributeMap . get ( attr ) [ 0 ] ;
187
251
fixers . push ( {
188
- range : [ attr . range [ 0 ] , attr . range [ 1 ] ] ,
252
+ range : [ attr . range [ 0 ] , attrrangeEnd ] ,
189
253
text : sortedAttrText ,
190
254
} ) ;
191
255
} ) ;
@@ -202,7 +266,7 @@ const generateFixerFunction = (node, context, reservedList) => {
202
266
203
267
return fixer . replaceTextRange ( [ rangeStart , rangeEnd ] , source . substr ( rangeStart , rangeEnd - rangeStart ) ) ;
204
268
} ;
205
- } ;
269
+ }
206
270
207
271
/**
208
272
* Checks if the `reservedFirst` option is valid
@@ -331,15 +395,17 @@ module.exports = {
331
395
const noSortAlphabetically = configuration . noSortAlphabetically || false ;
332
396
const reservedFirst = configuration . reservedFirst || false ;
333
397
const reservedFirstError = validateReservedFirstConfig ( context , reservedFirst ) ;
334
- let reservedList = Array . isArray ( reservedFirst ) ? reservedFirst : RESERVED_PROPS_LIST ;
398
+ const reservedList = Array . isArray ( reservedFirst ) ? reservedFirst : RESERVED_PROPS_LIST ;
335
399
const locale = configuration . locale || 'auto' ;
336
400
337
401
return {
402
+ Program ( ) {
403
+ attributeMap = new WeakMap ( ) ;
404
+ } ,
405
+
338
406
JSXOpeningElement ( node ) {
339
407
// `dangerouslySetInnerHTML` is only "reserved" on DOM components
340
- if ( reservedFirst && ! jsxUtil . isDOMComponent ( node ) ) {
341
- reservedList = reservedList . filter ( ( prop ) => prop !== 'dangerouslySetInnerHTML' ) ;
342
- }
408
+ const nodeReservedList = reservedFirst && ! jsxUtil . isDOMComponent ( node ) ? reservedList . filter ( ( prop ) => prop !== 'dangerouslySetInnerHTML' ) : reservedList ;
343
409
344
410
node . attributes . reduce ( ( memo , decl , idx , attrs ) => {
345
411
if ( decl . type === 'JSXSpreadAttribute' ) {
@@ -352,8 +418,6 @@ module.exports = {
352
418
const currentValue = decl . value ;
353
419
const previousIsCallback = isCallbackPropName ( previousPropName ) ;
354
420
const currentIsCallback = isCallbackPropName ( currentPropName ) ;
355
- const previousIsMultiline = isMultilineProp ( memo ) ;
356
- const currentIsMultiline = isMultilineProp ( decl ) ;
357
421
358
422
if ( ignoreCase ) {
359
423
previousPropName = previousPropName . toLowerCase ( ) ;
@@ -366,14 +430,14 @@ module.exports = {
366
430
return memo ;
367
431
}
368
432
369
- const previousIsReserved = isReservedPropName ( previousPropName , reservedList ) ;
370
- const currentIsReserved = isReservedPropName ( currentPropName , reservedList ) ;
433
+ const previousIsReserved = isReservedPropName ( previousPropName , nodeReservedList ) ;
434
+ const currentIsReserved = isReservedPropName ( currentPropName , nodeReservedList ) ;
371
435
372
436
if ( previousIsReserved && ! currentIsReserved ) {
373
437
return decl ;
374
438
}
375
439
if ( ! previousIsReserved && currentIsReserved ) {
376
- reportNodeAttribute ( decl , 'listReservedPropsFirst' , node , context , reservedList ) ;
440
+ reportNodeAttribute ( decl , 'listReservedPropsFirst' , node , context , nodeReservedList ) ;
377
441
378
442
return memo ;
379
443
}
@@ -386,7 +450,7 @@ module.exports = {
386
450
}
387
451
if ( previousIsCallback && ! currentIsCallback ) {
388
452
// Encountered a non-callback prop after a callback prop
389
- reportNodeAttribute ( memo , 'listCallbacksLast' , node , context , reservedList ) ;
453
+ reportNodeAttribute ( memo , 'listCallbacksLast' , node , context , nodeReservedList ) ;
390
454
391
455
return memo ;
392
456
}
@@ -397,7 +461,7 @@ module.exports = {
397
461
return decl ;
398
462
}
399
463
if ( ! currentValue && previousValue ) {
400
- reportNodeAttribute ( decl , 'listShorthandFirst' , node , context , reservedList ) ;
464
+ reportNodeAttribute ( decl , 'listShorthandFirst' , node , context , nodeReservedList ) ;
401
465
402
466
return memo ;
403
467
}
@@ -408,33 +472,33 @@ module.exports = {
408
472
return decl ;
409
473
}
410
474
if ( currentValue && ! previousValue ) {
411
- reportNodeAttribute ( memo , 'listShorthandLast' , node , context , reservedList ) ;
475
+ reportNodeAttribute ( memo , 'listShorthandLast' , node , context , nodeReservedList ) ;
412
476
413
477
return memo ;
414
478
}
415
479
}
416
480
481
+ const previousIsMultiline = isMultilineProp ( memo ) ;
482
+ const currentIsMultiline = isMultilineProp ( decl ) ;
417
483
if ( multiline === 'first' ) {
418
484
if ( previousIsMultiline && ! currentIsMultiline ) {
419
485
// Exiting the multiline prop section
420
486
return decl ;
421
487
}
422
488
if ( ! previousIsMultiline && currentIsMultiline ) {
423
489
// Encountered a non-multiline prop before a multiline prop
424
- reportNodeAttribute ( decl , 'listMultilineFirst' , node , context , reservedList ) ;
490
+ reportNodeAttribute ( decl , 'listMultilineFirst' , node , context , nodeReservedList ) ;
425
491
426
492
return memo ;
427
493
}
428
- }
429
-
430
- if ( multiline === 'last' ) {
494
+ } else if ( multiline === 'last' ) {
431
495
if ( ! previousIsMultiline && currentIsMultiline ) {
432
496
// Entering the multiline prop section
433
497
return decl ;
434
498
}
435
499
if ( previousIsMultiline && ! currentIsMultiline ) {
436
500
// Encountered a non-multiline prop after a multiline prop
437
- reportNodeAttribute ( memo , 'listMultilineLast' , node , context , reservedList ) ;
501
+ reportNodeAttribute ( memo , 'listMultilineLast' , node , context , nodeReservedList ) ;
438
502
439
503
return memo ;
440
504
}
@@ -448,7 +512,7 @@ module.exports = {
448
512
: previousPropName > currentPropName
449
513
)
450
514
) {
451
- reportNodeAttribute ( decl , 'sortPropsByAlpha' , node , context , reservedList ) ;
515
+ reportNodeAttribute ( decl , 'sortPropsByAlpha' , node , context , nodeReservedList ) ;
452
516
453
517
return memo ;
454
518
}
0 commit comments