@@ -46,10 +46,27 @@ function isReservedPropName(name, list) {
4646 return list . indexOf ( name ) >= 0 ;
4747}
4848
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+
4957function contextCompare ( a , b , options ) {
5058 let aProp = propName ( a ) ;
5159 let bProp = propName ( b ) ;
5260
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+
5370 if ( options . reservedFirst ) {
5471 const aIsReserved = isReservedPropName ( aProp , options . reservedList ) ;
5572 const bIsReserved = isReservedPropName ( bProp , options . reservedList ) ;
@@ -118,32 +135,79 @@ function contextCompare(a, b, options) {
118135 * Create an array of arrays where each subarray is composed of attributes
119136 * that are considered sortable.
120137 * @param {Array<JSXSpreadAttribute|JSXAttribute> } attributes
138+ * @param {Object } context The context of the rule
121139 * @return {Array<Array<JSXAttribute>> }
122140 */
123- function getGroupsOfSortableAttributes ( attributes ) {
141+ function getGroupsOfSortableAttributes ( attributes , context ) {
142+ const sourceCode = context . getSourceCode ( ) ;
143+
124144 const sortableAttributeGroups = [ ] ;
125145 let groupCount = 0 ;
146+ function addtoSortableAttributeGroups ( attribute ) {
147+ sortableAttributeGroups [ groupCount - 1 ] . push ( attribute ) ;
148+ }
149+
126150 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 ) { /**/ }
127158 const lastAttr = attributes [ i - 1 ] ;
159+ const attrIsSpread = attribute . type === 'JSXSpreadAttribute' ;
160+
128161 // If we have no groups or if the last attribute was JSXSpreadAttribute
129162 // then we start a new group. Append attributes to the group until we
130163 // come across another JSXSpreadAttribute or exhaust the array.
131164 if (
132165 ! lastAttr
133- || ( lastAttr . type === 'JSXSpreadAttribute'
134- && attributes [ i ] . type !== 'JSXSpreadAttribute' )
166+ || ( lastAttr . type === 'JSXSpreadAttribute' && ! attrIsSpread )
135167 ) {
136168 groupCount += 1 ;
137169 sortableAttributeGroups [ groupCount - 1 ] = [ ] ;
138170 }
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+ }
141205 }
142206 }
143207 return sortableAttributeGroups ;
144208}
145209
146- const generateFixerFunction = ( node , context , reservedList ) => {
210+ function generateFixerFunction ( node , context , reservedList ) {
147211 const sourceCode = context . getSourceCode ( ) ;
148212 const attributes = node . attributes . slice ( 0 ) ;
149213 const configuration = context . options [ 0 ] || { } ;
@@ -170,7 +234,7 @@ const generateFixerFunction = (node, context, reservedList) => {
170234 reservedList,
171235 locale,
172236 } ;
173- const sortableAttributeGroups = getGroupsOfSortableAttributes ( attributes ) ;
237+ const sortableAttributeGroups = getGroupsOfSortableAttributes ( attributes , context ) ;
174238 const sortedAttributeGroups = sortableAttributeGroups
175239 . slice ( 0 )
176240 . map ( ( group ) => group . slice ( 0 ) . sort ( ( a , b ) => contextCompare ( a , b , options ) ) ) ;
@@ -179,13 +243,13 @@ const generateFixerFunction = (node, context, reservedList) => {
179243 const fixers = [ ] ;
180244 let source = sourceCode . getText ( ) ;
181245
182- // Replace each unsorted attribute with the sorted one.
183246 sortableAttributeGroups . forEach ( ( sortableGroup , ii ) => {
184247 sortableGroup . forEach ( ( attr , jj ) => {
185248 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 ] ;
187251 fixers . push ( {
188- range : [ attr . range [ 0 ] , attr . range [ 1 ] ] ,
252+ range : [ attr . range [ 0 ] , attrrangeEnd ] ,
189253 text : sortedAttrText ,
190254 } ) ;
191255 } ) ;
@@ -202,7 +266,7 @@ const generateFixerFunction = (node, context, reservedList) => {
202266
203267 return fixer . replaceTextRange ( [ rangeStart , rangeEnd ] , source . substr ( rangeStart , rangeEnd - rangeStart ) ) ;
204268 } ;
205- } ;
269+ }
206270
207271/**
208272 * Checks if the `reservedFirst` option is valid
@@ -331,15 +395,17 @@ module.exports = {
331395 const noSortAlphabetically = configuration . noSortAlphabetically || false ;
332396 const reservedFirst = configuration . reservedFirst || false ;
333397 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 ;
335399 const locale = configuration . locale || 'auto' ;
336400
337401 return {
402+ Program ( ) {
403+ attributeMap = new WeakMap ( ) ;
404+ } ,
405+
338406 JSXOpeningElement ( node ) {
339407 // `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 ;
343409
344410 node . attributes . reduce ( ( memo , decl , idx , attrs ) => {
345411 if ( decl . type === 'JSXSpreadAttribute' ) {
@@ -352,8 +418,6 @@ module.exports = {
352418 const currentValue = decl . value ;
353419 const previousIsCallback = isCallbackPropName ( previousPropName ) ;
354420 const currentIsCallback = isCallbackPropName ( currentPropName ) ;
355- const previousIsMultiline = isMultilineProp ( memo ) ;
356- const currentIsMultiline = isMultilineProp ( decl ) ;
357421
358422 if ( ignoreCase ) {
359423 previousPropName = previousPropName . toLowerCase ( ) ;
@@ -366,14 +430,14 @@ module.exports = {
366430 return memo ;
367431 }
368432
369- const previousIsReserved = isReservedPropName ( previousPropName , reservedList ) ;
370- const currentIsReserved = isReservedPropName ( currentPropName , reservedList ) ;
433+ const previousIsReserved = isReservedPropName ( previousPropName , nodeReservedList ) ;
434+ const currentIsReserved = isReservedPropName ( currentPropName , nodeReservedList ) ;
371435
372436 if ( previousIsReserved && ! currentIsReserved ) {
373437 return decl ;
374438 }
375439 if ( ! previousIsReserved && currentIsReserved ) {
376- reportNodeAttribute ( decl , 'listReservedPropsFirst' , node , context , reservedList ) ;
440+ reportNodeAttribute ( decl , 'listReservedPropsFirst' , node , context , nodeReservedList ) ;
377441
378442 return memo ;
379443 }
@@ -386,7 +450,7 @@ module.exports = {
386450 }
387451 if ( previousIsCallback && ! currentIsCallback ) {
388452 // Encountered a non-callback prop after a callback prop
389- reportNodeAttribute ( memo , 'listCallbacksLast' , node , context , reservedList ) ;
453+ reportNodeAttribute ( memo , 'listCallbacksLast' , node , context , nodeReservedList ) ;
390454
391455 return memo ;
392456 }
@@ -397,7 +461,7 @@ module.exports = {
397461 return decl ;
398462 }
399463 if ( ! currentValue && previousValue ) {
400- reportNodeAttribute ( decl , 'listShorthandFirst' , node , context , reservedList ) ;
464+ reportNodeAttribute ( decl , 'listShorthandFirst' , node , context , nodeReservedList ) ;
401465
402466 return memo ;
403467 }
@@ -408,33 +472,33 @@ module.exports = {
408472 return decl ;
409473 }
410474 if ( currentValue && ! previousValue ) {
411- reportNodeAttribute ( memo , 'listShorthandLast' , node , context , reservedList ) ;
475+ reportNodeAttribute ( memo , 'listShorthandLast' , node , context , nodeReservedList ) ;
412476
413477 return memo ;
414478 }
415479 }
416480
481+ const previousIsMultiline = isMultilineProp ( memo ) ;
482+ const currentIsMultiline = isMultilineProp ( decl ) ;
417483 if ( multiline === 'first' ) {
418484 if ( previousIsMultiline && ! currentIsMultiline ) {
419485 // Exiting the multiline prop section
420486 return decl ;
421487 }
422488 if ( ! previousIsMultiline && currentIsMultiline ) {
423489 // Encountered a non-multiline prop before a multiline prop
424- reportNodeAttribute ( decl , 'listMultilineFirst' , node , context , reservedList ) ;
490+ reportNodeAttribute ( decl , 'listMultilineFirst' , node , context , nodeReservedList ) ;
425491
426492 return memo ;
427493 }
428- }
429-
430- if ( multiline === 'last' ) {
494+ } else if ( multiline === 'last' ) {
431495 if ( ! previousIsMultiline && currentIsMultiline ) {
432496 // Entering the multiline prop section
433497 return decl ;
434498 }
435499 if ( previousIsMultiline && ! currentIsMultiline ) {
436500 // Encountered a non-multiline prop after a multiline prop
437- reportNodeAttribute ( memo , 'listMultilineLast' , node , context , reservedList ) ;
501+ reportNodeAttribute ( memo , 'listMultilineLast' , node , context , nodeReservedList ) ;
438502
439503 return memo ;
440504 }
@@ -448,7 +512,7 @@ module.exports = {
448512 : previousPropName > currentPropName
449513 )
450514 ) {
451- reportNodeAttribute ( decl , 'sortPropsByAlpha' , node , context , reservedList ) ;
515+ reportNodeAttribute ( decl , 'sortPropsByAlpha' , node , context , nodeReservedList ) ;
452516
453517 return memo ;
454518 }
0 commit comments