@@ -40,6 +40,77 @@ function isReservedPropName(name, list) {
4040 return list . indexOf ( name ) >= 0 ;
4141}
4242
43+ function alphabeticalCompare ( a , b , ignoreCase ) {
44+ if ( ignoreCase ) {
45+ a = a . toLowerCase ( ) ;
46+ b = b . toLowerCase ( ) ;
47+ }
48+ return a . localeCompare ( b ) ;
49+ }
50+
51+ /**
52+ * Create an array of arrays where each subarray is composed of attributes
53+ * that are considered sortable.
54+ * @param {Array<JSXSpreadAttribute|JSXAttribute> } attributes
55+ * @return {Array<Array<JSXAttribute> }
56+ */
57+ function getGroupsOfSortableAttributes ( attributes ) {
58+ const sortableAttributeGroups = [ ] ;
59+ let groupCount = 0 ;
60+ for ( let i = 0 ; i < attributes . length ; i ++ ) {
61+ const lastAttr = attributes [ i - 1 ] ;
62+ // If we have no groups or if the last attribute was JSXSpreadAttribute
63+ // then we start a new group. Append attributes to the group until we
64+ // come across another JSXSpreadAttribute or exhaust the array.
65+ if (
66+ ! lastAttr ||
67+ ( lastAttr . type === 'JSXSpreadAttribute' &&
68+ attributes [ i ] . type !== 'JSXSpreadAttribute' )
69+ ) {
70+ groupCount ++ ;
71+ sortableAttributeGroups [ groupCount - 1 ] = [ ] ;
72+ }
73+ if ( attributes [ i ] . type !== 'JSXSpreadAttribute' ) {
74+ sortableAttributeGroups [ groupCount - 1 ] . push ( attributes [ i ] ) ;
75+ }
76+ }
77+ return sortableAttributeGroups ;
78+ }
79+
80+ const generateFixerFunction = ( node , context ) => {
81+ const sourceCode = context . getSourceCode ( ) ;
82+ const attributes = node . attributes . slice ( 0 ) ;
83+ const configuration = context . options [ 0 ] || { } ;
84+ const ignoreCase = configuration . ignoreCase || false ;
85+
86+ // Sort props according to the context. Only supports ignoreCase.
87+ // Since we cannot safely move JSXSpreadAttribute (due to potential variable overrides),
88+ // we only consider groups of sortable attributes.
89+ const sortableAttributeGroups = getGroupsOfSortableAttributes ( attributes ) ;
90+ const sortedAttributeGroups = sortableAttributeGroups . slice ( 0 ) . map ( group =>
91+ group . slice ( 0 ) . sort ( ( a , b ) =>
92+ alphabeticalCompare ( propName ( a ) , propName ( b ) , ignoreCase )
93+ )
94+ ) ;
95+
96+ return function ( fixer ) {
97+ const fixers = [ ] ;
98+
99+ // Replace each unsorted attribute with the sorted one.
100+ sortableAttributeGroups . forEach ( ( sortableGroup , ii ) => {
101+ sortableGroup . forEach ( ( attr , jj ) => {
102+ const sortedAttr = sortedAttributeGroups [ ii ] [ jj ] ;
103+ const sortedAttrText = sourceCode . getText ( sortedAttr ) ;
104+ fixers . push (
105+ fixer . replaceTextRange ( [ attr . start , attr . end ] , sortedAttrText )
106+ ) ;
107+ } ) ;
108+ } ) ;
109+
110+ return fixers ;
111+ } ;
112+ } ;
113+
43114/**
44115 * Checks if the `reservedFirst` option is valid
45116 * @param {Object } context The context of the rule
@@ -88,7 +159,7 @@ module.exports = {
88159 category : 'Stylistic Issues' ,
89160 recommended : false
90161 } ,
91-
162+ fixable : 'code' ,
92163 schema : [ {
93164 type : 'object' ,
94165 properties : {
@@ -168,7 +239,8 @@ module.exports = {
168239 if ( ! noSortAlphabetically && currentPropName < previousPropName ) {
169240 context . report ( {
170241 node : decl ,
171- message : 'Props should be sorted alphabetically'
242+ message : 'Props should be sorted alphabetically' ,
243+ fix : generateFixerFunction ( node , context )
172244 } ) ;
173245 return memo ;
174246 }
@@ -228,7 +300,8 @@ module.exports = {
228300 if ( ! noSortAlphabetically && currentPropName < previousPropName ) {
229301 context . report ( {
230302 node : decl ,
231- message : 'Props should be sorted alphabetically'
303+ message : 'Props should be sorted alphabetically' ,
304+ fix : generateFixerFunction ( node , context )
232305 } ) ;
233306 return memo ;
234307 }
0 commit comments