@@ -40,6 +40,77 @@ function isReservedPropName(name, list) {
40
40
return list . indexOf ( name ) >= 0 ;
41
41
}
42
42
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
+
43
114
/**
44
115
* Checks if the `reservedFirst` option is valid
45
116
* @param {Object } context The context of the rule
@@ -88,7 +159,7 @@ module.exports = {
88
159
category : 'Stylistic Issues' ,
89
160
recommended : false
90
161
} ,
91
-
162
+ fixable : 'code' ,
92
163
schema : [ {
93
164
type : 'object' ,
94
165
properties : {
@@ -168,7 +239,8 @@ module.exports = {
168
239
if ( ! noSortAlphabetically && currentPropName < previousPropName ) {
169
240
context . report ( {
170
241
node : decl ,
171
- message : 'Props should be sorted alphabetically'
242
+ message : 'Props should be sorted alphabetically' ,
243
+ fix : generateFixerFunction ( node , context )
172
244
} ) ;
173
245
return memo ;
174
246
}
@@ -228,7 +300,8 @@ module.exports = {
228
300
if ( ! noSortAlphabetically && currentPropName < previousPropName ) {
229
301
context . report ( {
230
302
node : decl ,
231
- message : 'Props should be sorted alphabetically'
303
+ message : 'Props should be sorted alphabetically' ,
304
+ fix : generateFixerFunction ( node , context )
232
305
} ) ;
233
306
return memo ;
234
307
}
0 commit comments