@@ -46,6 +46,14 @@ 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 ) ;
@@ -98,6 +106,17 @@ function contextCompare(a, b, options) {
98106 return 0 ;
99107 }
100108
109+ if ( options . commentbetween ) {
110+ const aSortToEnd = shouldSortToEnd ( a ) ;
111+ const bSortToEnd = shouldSortToEnd ( b ) ;
112+ if ( aSortToEnd && ! bSortToEnd ) {
113+ return 1 ;
114+ }
115+ if ( ! aSortToEnd && bSortToEnd ) {
116+ return - 1 ;
117+ }
118+ }
119+
101120 const actualLocale = options . locale === 'auto' ? undefined : options . locale ;
102121
103122 if ( options . ignoreCase ) {
@@ -118,32 +137,77 @@ function contextCompare(a, b, options) {
118137 * Create an array of arrays where each subarray is composed of attributes
119138 * that are considered sortable.
120139 * @param {Array<JSXSpreadAttribute|JSXAttribute> } attributes
140+ * @param {Object } context The context of the rule
121141 * @return {Array<Array<JSXAttribute>> }
122142 */
123- function getGroupsOfSortableAttributes ( attributes ) {
143+ function getGroupsOfSortableAttributes ( attributes , context ) {
144+ const sourceCode = context . getSourceCode ( ) ;
145+
124146 const sortableAttributeGroups = [ ] ;
125147 let groupCount = 0 ;
148+ function addtoSortableAttributeGroups ( attribute ) {
149+ sortableAttributeGroups [ groupCount - 1 ] . push ( attribute ) ;
150+ }
151+
126152 for ( let i = 0 ; i < attributes . length ; i ++ ) {
153+ const attribute = attributes [ i ] ;
154+ const nextAttribute = attributes [ i + 1 ] ;
155+ const attributeline = attribute . loc . start . line ;
156+ const comment = sourceCode . getCommentsAfter ( attribute ) ;
127157 const lastAttr = attributes [ i - 1 ] ;
158+
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 ++ ;
183+ } else if ( attributeline === commentline ) {
184+ if ( firstComment . type === 'Block' ) {
185+ attributeMap . set ( attribute , [ nextAttribute . range [ 1 ] , true ] ) ;
186+ i ++ ;
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 ++ ;
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 ] || { } ;
@@ -155,6 +219,7 @@ const generateFixerFunction = (node, context, reservedList) => {
155219 const noSortAlphabetically = configuration . noSortAlphabetically || false ;
156220 const reservedFirst = configuration . reservedFirst || false ;
157221 const locale = configuration . locale || 'auto' ;
222+ const commentbetween = configuration . commentbetween || true ;
158223
159224 // Sort props according to the context. Only supports ignoreCase.
160225 // Since we cannot safely move JSXSpreadAttribute (due to potential variable overrides),
@@ -169,8 +234,9 @@ const generateFixerFunction = (node, context, reservedList) => {
169234 reservedFirst,
170235 reservedList,
171236 locale,
237+ commentbetween,
172238 } ;
173- const sortableAttributeGroups = getGroupsOfSortableAttributes ( attributes ) ;
239+ const sortableAttributeGroups = getGroupsOfSortableAttributes ( attributes , context ) ;
174240 const sortedAttributeGroups = sortableAttributeGroups
175241 . slice ( 0 )
176242 . map ( ( group ) => group . slice ( 0 ) . sort ( ( a , b ) => contextCompare ( a , b , options ) ) ) ;
@@ -179,13 +245,13 @@ const generateFixerFunction = (node, context, reservedList) => {
179245 const fixers = [ ] ;
180246 let source = sourceCode . getText ( ) ;
181247
182- // Replace each unsorted attribute with the sorted one.
183248 sortableAttributeGroups . forEach ( ( sortableGroup , ii ) => {
184249 sortableGroup . forEach ( ( attr , jj ) => {
185250 const sortedAttr = sortedAttributeGroups [ ii ] [ jj ] ;
186- const sortedAttrText = sourceCode . getText ( sortedAttr ) ;
251+ const sortedAttrText = source . substring ( sortedAttr . range [ 0 ] , attributeMap . get ( sortedAttr ) [ 0 ] ) ;
252+ const attrrangeEnd = attributeMap . get ( attr ) [ 0 ] ;
187253 fixers . push ( {
188- range : [ attr . range [ 0 ] , attr . range [ 1 ] ] ,
254+ range : [ attr . range [ 0 ] , attrrangeEnd ] ,
189255 text : sortedAttrText ,
190256 } ) ;
191257 } ) ;
@@ -202,7 +268,7 @@ const generateFixerFunction = (node, context, reservedList) => {
202268
203269 return fixer . replaceTextRange ( [ rangeStart , rangeEnd ] , source . substr ( rangeStart , rangeEnd - rangeStart ) ) ;
204270 } ;
205- } ;
271+ }
206272
207273/**
208274 * Checks if the `reservedFirst` option is valid
@@ -331,15 +397,17 @@ module.exports = {
331397 const noSortAlphabetically = configuration . noSortAlphabetically || false ;
332398 const reservedFirst = configuration . reservedFirst || false ;
333399 const reservedFirstError = validateReservedFirstConfig ( context , reservedFirst ) ;
334- let reservedList = Array . isArray ( reservedFirst ) ? reservedFirst : RESERVED_PROPS_LIST ;
400+ const reservedList = Array . isArray ( reservedFirst ) ? reservedFirst : RESERVED_PROPS_LIST ;
335401 const locale = configuration . locale || 'auto' ;
336402
337403 return {
404+ Program ( ) {
405+ attributeMap = new WeakMap ( ) ;
406+ } ,
407+
338408 JSXOpeningElement ( node ) {
339409 // `dangerouslySetInnerHTML` is only "reserved" on DOM components
340- if ( reservedFirst && ! jsxUtil . isDOMComponent ( node ) ) {
341- reservedList = reservedList . filter ( ( prop ) => prop !== 'dangerouslySetInnerHTML' ) ;
342- }
410+ const nodeReservedList = reservedFirst && ! jsxUtil . isDOMComponent ( node ) ? reservedList . filter ( ( prop ) => prop !== 'dangerouslySetInnerHTML' ) : reservedList ;
343411
344412 node . attributes . reduce ( ( memo , decl , idx , attrs ) => {
345413 if ( decl . type === 'JSXSpreadAttribute' ) {
@@ -352,8 +420,6 @@ module.exports = {
352420 const currentValue = decl . value ;
353421 const previousIsCallback = isCallbackPropName ( previousPropName ) ;
354422 const currentIsCallback = isCallbackPropName ( currentPropName ) ;
355- const previousIsMultiline = isMultilineProp ( memo ) ;
356- const currentIsMultiline = isMultilineProp ( decl ) ;
357423
358424 if ( ignoreCase ) {
359425 previousPropName = previousPropName . toLowerCase ( ) ;
@@ -366,14 +432,14 @@ module.exports = {
366432 return memo ;
367433 }
368434
369- const previousIsReserved = isReservedPropName ( previousPropName , reservedList ) ;
370- const currentIsReserved = isReservedPropName ( currentPropName , reservedList ) ;
435+ const previousIsReserved = isReservedPropName ( previousPropName , nodeReservedList ) ;
436+ const currentIsReserved = isReservedPropName ( currentPropName , nodeReservedList ) ;
371437
372438 if ( previousIsReserved && ! currentIsReserved ) {
373439 return decl ;
374440 }
375441 if ( ! previousIsReserved && currentIsReserved ) {
376- reportNodeAttribute ( decl , 'listReservedPropsFirst' , node , context , reservedList ) ;
442+ reportNodeAttribute ( decl , 'listReservedPropsFirst' , node , context , nodeReservedList ) ;
377443
378444 return memo ;
379445 }
@@ -386,7 +452,7 @@ module.exports = {
386452 }
387453 if ( previousIsCallback && ! currentIsCallback ) {
388454 // Encountered a non-callback prop after a callback prop
389- reportNodeAttribute ( memo , 'listCallbacksLast' , node , context , reservedList ) ;
455+ reportNodeAttribute ( memo , 'listCallbacksLast' , node , context , nodeReservedList ) ;
390456
391457 return memo ;
392458 }
@@ -397,7 +463,7 @@ module.exports = {
397463 return decl ;
398464 }
399465 if ( ! currentValue && previousValue ) {
400- reportNodeAttribute ( decl , 'listShorthandFirst' , node , context , reservedList ) ;
466+ reportNodeAttribute ( decl , 'listShorthandFirst' , node , context , nodeReservedList ) ;
401467
402468 return memo ;
403469 }
@@ -408,33 +474,33 @@ module.exports = {
408474 return decl ;
409475 }
410476 if ( currentValue && ! previousValue ) {
411- reportNodeAttribute ( memo , 'listShorthandLast' , node , context , reservedList ) ;
477+ reportNodeAttribute ( memo , 'listShorthandLast' , node , context , nodeReservedList ) ;
412478
413479 return memo ;
414480 }
415481 }
416482
483+ const previousIsMultiline = isMultilineProp ( memo ) ;
484+ const currentIsMultiline = isMultilineProp ( decl ) ;
417485 if ( multiline === 'first' ) {
418486 if ( previousIsMultiline && ! currentIsMultiline ) {
419487 // Exiting the multiline prop section
420488 return decl ;
421489 }
422490 if ( ! previousIsMultiline && currentIsMultiline ) {
423491 // Encountered a non-multiline prop before a multiline prop
424- reportNodeAttribute ( decl , 'listMultilineFirst' , node , context , reservedList ) ;
492+ reportNodeAttribute ( decl , 'listMultilineFirst' , node , context , nodeReservedList ) ;
425493
426494 return memo ;
427495 }
428- }
429-
430- if ( multiline === 'last' ) {
496+ } else if ( multiline === 'last' ) {
431497 if ( ! previousIsMultiline && currentIsMultiline ) {
432498 // Entering the multiline prop section
433499 return decl ;
434500 }
435501 if ( previousIsMultiline && ! currentIsMultiline ) {
436502 // Encountered a non-multiline prop after a multiline prop
437- reportNodeAttribute ( memo , 'listMultilineLast' , node , context , reservedList ) ;
503+ reportNodeAttribute ( memo , 'listMultilineLast' , node , context , nodeReservedList ) ;
438504
439505 return memo ;
440506 }
@@ -448,7 +514,7 @@ module.exports = {
448514 : previousPropName > currentPropName
449515 )
450516 ) {
451- reportNodeAttribute ( decl , 'sortPropsByAlpha' , node , context , reservedList ) ;
517+ reportNodeAttribute ( decl , 'sortPropsByAlpha' , node , context , nodeReservedList ) ;
452518
453519 return memo ;
454520 }
0 commit comments