@@ -28,8 +28,10 @@ function isMultilineProp(node) {
28
28
29
29
const messages = {
30
30
noUnreservedProps : 'A customized reserved first list must only contain a subset of React reserved props. Remove: {{unreservedWords}}' ,
31
- listIsEmpty : 'A customized reserved first list must not be empty' ,
31
+ reservedListIsEmpty : 'A customized reserved first list must not be empty' ,
32
+ customPropsListIsEmpty : 'Custom props first list must not be empty' ,
32
33
listReservedPropsFirst : 'Reserved props must be listed before all other props' ,
34
+ listCustomPropsFirst : 'Custom props must be listed before all other props' ,
33
35
listCallbacksLast : 'Callbacks must be listed after all other props' ,
34
36
listShorthandFirst : 'Shorthand props must be listed before all other props' ,
35
37
listShorthandLast : 'Shorthand props must be listed after all other props' ,
@@ -45,7 +47,7 @@ const RESERVED_PROPS_LIST = [
45
47
'ref' ,
46
48
] ;
47
49
48
- function isReservedPropName ( name , list ) {
50
+ function isPropNameInList ( name , list ) {
49
51
return list . indexOf ( name ) >= 0 ;
50
52
}
51
53
@@ -71,8 +73,8 @@ function contextCompare(a, b, options) {
71
73
}
72
74
73
75
if ( options . reservedFirst ) {
74
- const aIsReserved = isReservedPropName ( aProp , options . reservedList ) ;
75
- const bIsReserved = isReservedPropName ( bProp , options . reservedList ) ;
76
+ const aIsReserved = isPropNameInList ( aProp , options . reservedList ) ;
77
+ const bIsReserved = isPropNameInList ( bProp , options . reservedList ) ;
76
78
if ( aIsReserved && ! bIsReserved ) {
77
79
return - 1 ;
78
80
}
@@ -81,6 +83,17 @@ function contextCompare(a, b, options) {
81
83
}
82
84
}
83
85
86
+ if ( options . customPropsList ) {
87
+ const aIsCustom = isPropNameInList ( aProp , options . customPropsList ) ;
88
+ const bIsCustom = isPropNameInList ( bProp , options . customPropsList ) ;
89
+ if ( aIsCustom && ! bIsCustom ) {
90
+ return - 1 ;
91
+ }
92
+ if ( ! aIsCustom && bIsCustom ) {
93
+ return 1 ;
94
+ }
95
+ }
96
+
84
97
if ( options . callbacksLast ) {
85
98
const aIsCallback = propTypesSortUtil . isCallbackPropName ( aProp ) ;
86
99
const bIsCallback = propTypesSortUtil . isCallbackPropName ( bProp ) ;
@@ -212,7 +225,7 @@ function getGroupsOfSortableAttributes(attributes, context) {
212
225
return sortableAttributeGroups ;
213
226
}
214
227
215
- function generateFixerFunction ( node , context , reservedList ) {
228
+ function generateFixerFunction ( node , context , reservedList , customPropsList ) {
216
229
const attributes = node . attributes . slice ( 0 ) ;
217
230
const configuration = context . options [ 0 ] || { } ;
218
231
const ignoreCase = configuration . ignoreCase || false ;
@@ -222,11 +235,9 @@ function generateFixerFunction(node, context, reservedList) {
222
235
const multiline = configuration . multiline || 'ignore' ;
223
236
const noSortAlphabetically = configuration . noSortAlphabetically || false ;
224
237
const reservedFirst = configuration . reservedFirst || false ;
238
+ const customPropsFirst = configuration . customPropsFirst || false ;
225
239
const locale = configuration . locale || 'auto' ;
226
240
227
- // Sort props according to the context. Only supports ignoreCase.
228
- // Since we cannot safely move JSXSpreadAttribute (due to potential variable overrides),
229
- // we only consider groups of sortable attributes.
230
241
const options = {
231
242
ignoreCase,
232
243
callbacksLast,
@@ -236,8 +247,11 @@ function generateFixerFunction(node, context, reservedList) {
236
247
noSortAlphabetically,
237
248
reservedFirst,
238
249
reservedList,
250
+ customPropsFirst,
251
+ customPropsList,
239
252
locale,
240
253
} ;
254
+
241
255
const sortableAttributeGroups = getGroupsOfSortableAttributes ( attributes , context ) ;
242
256
const sortedAttributeGroups = sortableAttributeGroups
243
257
. slice ( 0 )
@@ -284,14 +298,14 @@ function validateReservedFirstConfig(context, reservedFirst) {
284
298
if ( reservedFirst ) {
285
299
if ( Array . isArray ( reservedFirst ) ) {
286
300
// Only allow a subset of reserved words in customized lists
287
- const nonReservedWords = reservedFirst . filter ( ( word ) => ! isReservedPropName (
301
+ const nonReservedWords = reservedFirst . filter ( ( word ) => ! isPropNameInList (
288
302
word ,
289
303
RESERVED_PROPS_LIST
290
304
) ) ;
291
305
292
306
if ( reservedFirst . length === 0 ) {
293
307
return function Report ( decl ) {
294
- report ( context , messages . listIsEmpty , 'listIsEmpty ' , {
308
+ report ( context , messages . reservedListIsEmpty , 'reservedListIsEmpty ' , {
295
309
node : decl ,
296
310
} ) ;
297
311
} ;
@@ -310,6 +324,27 @@ function validateReservedFirstConfig(context, reservedFirst) {
310
324
}
311
325
}
312
326
327
+ /**
328
+ * Checks if the `customPropsFirst` option is valid
329
+ * @param {Object } context The context of the rule
330
+ * @param {boolean | string[] } customPropsFirst The `customPropsFirst` option
331
+ * @return {Function | undefined } If an error is detected, a function to generate the error message, otherwise, `undefined`
332
+ */
333
+ // eslint-disable-next-line consistent-return
334
+ function validateCustomPropsFirstConfig ( context , customPropsFirst ) {
335
+ if ( customPropsFirst ) {
336
+ if ( Array . isArray ( customPropsFirst ) ) {
337
+ if ( customPropsFirst . length === 0 ) {
338
+ return function Report ( decl ) {
339
+ report ( context , messages . customPropsListIsEmpty , 'customPropsListIsEmpty' , {
340
+ node : decl ,
341
+ } ) ;
342
+ } ;
343
+ }
344
+ }
345
+ }
346
+ }
347
+
313
348
const reportedNodeAttributes = new WeakMap ( ) ;
314
349
/**
315
350
* Check if the current node attribute has already been reported with the same error type
@@ -320,8 +355,9 @@ const reportedNodeAttributes = new WeakMap();
320
355
* @param {Object } node The parent node for the node attribute
321
356
* @param {Object } context The context of the rule
322
357
* @param {Array<String> } reservedList The list of reserved props
358
+ * @param {Array<String> } customPropsList The list of custom props
323
359
*/
324
- function reportNodeAttribute ( nodeAttribute , errorType , node , context , reservedList ) {
360
+ function reportNodeAttribute ( nodeAttribute , errorType , node , context , reservedList , customPropsList ) {
325
361
const errors = reportedNodeAttributes . get ( nodeAttribute ) || [ ] ;
326
362
327
363
if ( includes ( errors , errorType ) ) {
@@ -334,7 +370,7 @@ function reportNodeAttribute(nodeAttribute, errorType, node, context, reservedLi
334
370
335
371
report ( context , messages [ errorType ] , errorType , {
336
372
node : nodeAttribute . name ,
337
- fix : generateFixerFunction ( node , context , reservedList ) ,
373
+ fix : generateFixerFunction ( node , context , reservedList , customPropsList ) ,
338
374
} ) ;
339
375
}
340
376
@@ -382,6 +418,9 @@ module.exports = {
382
418
reservedFirst : {
383
419
type : [ 'array' , 'boolean' ] ,
384
420
} ,
421
+ customPropsFirst : {
422
+ type : [ 'array' , 'boolean' ] ,
423
+ } ,
385
424
locale : {
386
425
type : 'string' ,
387
426
default : 'auto' ,
@@ -402,6 +441,9 @@ module.exports = {
402
441
const reservedFirst = configuration . reservedFirst || false ;
403
442
const reservedFirstError = validateReservedFirstConfig ( context , reservedFirst ) ;
404
443
const reservedList = Array . isArray ( reservedFirst ) ? reservedFirst : RESERVED_PROPS_LIST ;
444
+ const customPropsFirst = configuration . customPropsFirst || false ;
445
+ const customPropsFirstError = validateCustomPropsFirstConfig ( context , customPropsFirst ) ;
446
+ const customPropsList = Array . isArray ( customPropsFirst ) ? customPropsFirst : [ ] ;
405
447
const locale = configuration . locale || 'auto' ;
406
448
407
449
return {
@@ -436,14 +478,33 @@ module.exports = {
436
478
return memo ;
437
479
}
438
480
439
- const previousIsReserved = isReservedPropName ( previousPropName , nodeReservedList ) ;
440
- const currentIsReserved = isReservedPropName ( currentPropName , nodeReservedList ) ;
481
+ const previousIsReserved = isPropNameInList ( previousPropName , nodeReservedList ) ;
482
+ const currentIsReserved = isPropNameInList ( currentPropName , nodeReservedList ) ;
441
483
442
484
if ( previousIsReserved && ! currentIsReserved ) {
443
485
return decl ;
444
486
}
445
487
if ( ! previousIsReserved && currentIsReserved ) {
446
- reportNodeAttribute ( decl , 'listReservedPropsFirst' , node , context , nodeReservedList ) ;
488
+ reportNodeAttribute ( decl , 'listReservedPropsFirst' , node , context , nodeReservedList , customPropsList ) ;
489
+
490
+ return memo ;
491
+ }
492
+ }
493
+
494
+ if ( customPropsFirst ) {
495
+ if ( customPropsFirstError ) {
496
+ customPropsFirstError ( decl ) ;
497
+ return memo ;
498
+ }
499
+
500
+ const previousIsCustom = isPropNameInList ( propName ( memo ) , customPropsList ) ;
501
+ const currentIsCustom = isPropNameInList ( propName ( decl ) , customPropsList ) ;
502
+
503
+ if ( previousIsCustom && ! currentIsCustom ) {
504
+ return decl ;
505
+ }
506
+ if ( ! previousIsCustom && currentIsCustom ) {
507
+ reportNodeAttribute ( decl , 'listCustomPropsFirst' , node , context , nodeReservedList , customPropsList ) ;
447
508
448
509
return memo ;
449
510
}
@@ -456,7 +517,7 @@ module.exports = {
456
517
}
457
518
if ( previousIsCallback && ! currentIsCallback ) {
458
519
// Encountered a non-callback prop after a callback prop
459
- reportNodeAttribute ( memo , 'listCallbacksLast' , node , context , nodeReservedList ) ;
520
+ reportNodeAttribute ( memo , 'listCallbacksLast' , node , context , nodeReservedList , customPropsList ) ;
460
521
461
522
return memo ;
462
523
}
@@ -467,7 +528,7 @@ module.exports = {
467
528
return decl ;
468
529
}
469
530
if ( ! currentValue && previousValue ) {
470
- reportNodeAttribute ( decl , 'listShorthandFirst' , node , context , nodeReservedList ) ;
531
+ reportNodeAttribute ( decl , 'listShorthandFirst' , node , context , nodeReservedList , customPropsList ) ;
471
532
472
533
return memo ;
473
534
}
@@ -478,7 +539,7 @@ module.exports = {
478
539
return decl ;
479
540
}
480
541
if ( currentValue && ! previousValue ) {
481
- reportNodeAttribute ( memo , 'listShorthandLast' , node , context , nodeReservedList ) ;
542
+ reportNodeAttribute ( memo , 'listShorthandLast' , node , context , nodeReservedList , customPropsList ) ;
482
543
483
544
return memo ;
484
545
}
@@ -493,7 +554,7 @@ module.exports = {
493
554
}
494
555
if ( ! previousIsMultiline && currentIsMultiline ) {
495
556
// Encountered a non-multiline prop before a multiline prop
496
- reportNodeAttribute ( decl , 'listMultilineFirst' , node , context , nodeReservedList ) ;
557
+ reportNodeAttribute ( decl , 'listMultilineFirst' , node , context , nodeReservedList , customPropsList ) ;
497
558
498
559
return memo ;
499
560
}
@@ -504,7 +565,7 @@ module.exports = {
504
565
}
505
566
if ( previousIsMultiline && ! currentIsMultiline ) {
506
567
// Encountered a non-multiline prop after a multiline prop
507
- reportNodeAttribute ( memo , 'listMultilineLast' , node , context , nodeReservedList ) ;
568
+ reportNodeAttribute ( memo , 'listMultilineLast' , node , context , nodeReservedList , customPropsList ) ;
508
569
509
570
return memo ;
510
571
}
@@ -518,7 +579,7 @@ module.exports = {
518
579
: previousPropName > currentPropName
519
580
)
520
581
) {
521
- reportNodeAttribute ( decl , 'sortPropsByAlpha' , node , context , nodeReservedList ) ;
582
+ reportNodeAttribute ( decl , 'sortPropsByAlpha' , node , context , nodeReservedList , customPropsList ) ;
522
583
523
584
return memo ;
524
585
}
0 commit comments