5
5
6
6
'use strict' ;
7
7
8
+ const entries = require ( 'object.entries' ) ;
9
+ const values = require ( 'object.values' ) ;
8
10
const Components = require ( '../util/Components' ) ;
9
11
const docsUrl = require ( '../util/docsUrl' ) ;
10
12
const astUtil = require ( '../util/ast' ) ;
@@ -17,6 +19,9 @@ const report = require('../util/report');
17
19
const messages = {
18
20
noDefaultWithRequired : 'propType "{{name}}" is required and should not have a defaultProps declaration.' ,
19
21
shouldHaveDefault : 'propType "{{name}}" is not required, but has no corresponding defaultProps declaration.' ,
22
+ noDefaultPropsWithFunction : 'Don’t use defaultProps with function components.' ,
23
+ shouldAssignObjectDefault : 'propType "{{name}}" is not required, but has no corresponding default argument value.' ,
24
+ destructureInSignature : 'Must destructure props in the function signature to initialize an optional prop.' ,
20
25
} ;
21
26
22
27
module . exports = {
@@ -35,6 +40,19 @@ module.exports = {
35
40
forbidDefaultForRequired : {
36
41
type : 'boolean' ,
37
42
} ,
43
+ classes : {
44
+ allow : {
45
+ enum : [ 'defaultProps' , 'ignore' ] ,
46
+ } ,
47
+ } ,
48
+ functions : {
49
+ allow : {
50
+ enum : [ 'defaultArguments' , 'defaultProps' , 'ignore' ] ,
51
+ } ,
52
+ } ,
53
+ /**
54
+ * @deprecated
55
+ */
38
56
ignoreFunctionalComponents : {
39
57
type : 'boolean' ,
40
58
} ,
@@ -46,7 +64,15 @@ module.exports = {
46
64
create : Components . detect ( ( context , components ) => {
47
65
const configuration = context . options [ 0 ] || { } ;
48
66
const forbidDefaultForRequired = configuration . forbidDefaultForRequired || false ;
49
- const ignoreFunctionalComponents = configuration . ignoreFunctionalComponents || false ;
67
+ const classes = configuration . classes || 'defaultProps' ;
68
+ /**
69
+ * @todo
70
+ * - Remove ignoreFunctionalComponents
71
+ * - Change default to 'defaultArguments'
72
+ */
73
+ const functions = configuration . ignoreFunctionalComponents
74
+ ? 'ignore'
75
+ : configuration . functions || 'defaultProps' ;
50
76
51
77
/**
52
78
* Reports all propTypes passed in that don't have a defaultProps counterpart.
@@ -55,14 +81,10 @@ module.exports = {
55
81
* @return {void }
56
82
*/
57
83
function reportPropTypesWithoutDefault ( propTypes , defaultProps ) {
58
- // If this defaultProps is "unresolved", then we should ignore this component and not report
59
- // any errors for it, to avoid false-positives with e.g. external defaultProps declarations or spread operators.
60
- if ( defaultProps === 'unresolved' ) {
61
- return ;
62
- }
84
+ entries ( propTypes ) . forEach ( ( propType ) => {
85
+ const propName = propType [ 0 ] ;
86
+ const prop = propType [ 1 ] ;
63
87
64
- Object . keys ( propTypes ) . forEach ( ( propName ) => {
65
- const prop = propTypes [ propName ] ;
66
88
if ( ! prop . node ) {
67
89
return ;
68
90
}
@@ -87,6 +109,48 @@ module.exports = {
87
109
} ) ;
88
110
}
89
111
112
+ /**
113
+ * If functions option is 'defaultArguments', reports defaultProps is used and all params that doesn't initialized.
114
+ * @param {Object } componentNode Node of component.
115
+ * @param {Object[] } declaredPropTypes List of propTypes to check `isRequired`.
116
+ * @param {Object } defaultProps Object of defaultProps to check used.
117
+ */
118
+ function reportFunctionComponent ( componentNode , declaredPropTypes , defaultProps ) {
119
+ if ( defaultProps ) {
120
+ report ( context , messages . noDefaultPropsWithFunction , 'noDefaultPropsWithFunction' , {
121
+ node : componentNode ,
122
+ } ) ;
123
+ }
124
+
125
+ const props = componentNode . params [ 0 ] ;
126
+ const propTypes = declaredPropTypes ;
127
+
128
+ if ( props . type === 'Identifier' ) {
129
+ const hasOptionalProp = values ( propTypes ) . some ( ( propType ) => ! propType . isRequired ) ;
130
+ if ( hasOptionalProp ) {
131
+ report ( context , messages . destructureInSignature , 'destructureInSignature' , {
132
+ node : props ,
133
+ } ) ;
134
+ }
135
+ } else if ( props . type === 'ObjectPattern' ) {
136
+ props . properties . filter ( ( prop ) => {
137
+ if ( prop . type === 'RestElement' || prop . type === 'ExperimentalRestProperty' ) {
138
+ return false ;
139
+ }
140
+ const propType = propTypes [ prop . key . name ] ;
141
+ if ( ! propType || propType . isRequired ) {
142
+ return false ;
143
+ }
144
+ return prop . value . type !== 'AssignmentPattern' ;
145
+ } ) . forEach ( ( prop ) => {
146
+ report ( context , messages . shouldAssignObjectDefault , 'shouldAssignObjectDefault' , {
147
+ node : prop ,
148
+ data : { name : prop . key . name } ,
149
+ } ) ;
150
+ } ) ;
151
+ }
152
+ }
153
+
90
154
// --------------------------------------------------------------------------
91
155
// Public API
92
156
// --------------------------------------------------------------------------
@@ -95,17 +159,33 @@ module.exports = {
95
159
'Program:exit' ( ) {
96
160
const list = components . list ( ) ;
97
161
98
- Object . keys ( list ) . filter ( ( component ) => {
99
- if ( ignoreFunctionalComponents
100
- && ( astUtil . isFunction ( list [ component ] . node ) || astUtil . isFunctionLikeExpression ( list [ component ] . node ) ) ) {
162
+ values ( list ) . filter ( ( component ) => {
163
+ if ( functions === 'ignore' && astUtil . isFunctionLike ( component . node ) ) {
101
164
return false ;
102
165
}
103
- return list [ component ] . declaredPropTypes ;
166
+ if ( classes === 'ignore' && astUtil . isClass ( component . node ) ) {
167
+ return false ;
168
+ }
169
+
170
+ // If this defaultProps is "unresolved", then we should ignore this component and not report
171
+ // any errors for it, to avoid false-positives with e.g. external defaultProps declarations or spread operators.
172
+ if ( component . defaultProps === 'unresolved' ) {
173
+ return false ;
174
+ }
175
+ return component . declaredPropTypes !== undefined ;
104
176
} ) . forEach ( ( component ) => {
105
- reportPropTypesWithoutDefault (
106
- list [ component ] . declaredPropTypes ,
107
- list [ component ] . defaultProps || { }
108
- ) ;
177
+ if ( functions === 'defaultArguments' && astUtil . isFunctionLike ( component . node ) ) {
178
+ reportFunctionComponent (
179
+ component . node ,
180
+ component . declaredPropTypes ,
181
+ component . defaultProps
182
+ ) ;
183
+ } else {
184
+ reportPropTypesWithoutDefault (
185
+ component . declaredPropTypes ,
186
+ component . defaultProps || { }
187
+ ) ;
188
+ }
109
189
} ) ;
110
190
} ,
111
191
} ;
0 commit comments