55
66'use strict' ;
77
8+ const entries = require ( 'object.entries' ) ;
9+ const values = require ( 'object.values' ) ;
810const Components = require ( '../util/Components' ) ;
911const docsUrl = require ( '../util/docsUrl' ) ;
1012const astUtil = require ( '../util/ast' ) ;
@@ -17,6 +19,9 @@ const report = require('../util/report');
1719const messages = {
1820 noDefaultWithRequired : 'propType "{{name}}" is required and should not have a defaultProps declaration.' ,
1921 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.' ,
2025} ;
2126
2227module . exports = {
@@ -35,6 +40,19 @@ module.exports = {
3540 forbidDefaultForRequired : {
3641 type : 'boolean' ,
3742 } ,
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+ */
3856 ignoreFunctionalComponents : {
3957 type : 'boolean' ,
4058 } ,
@@ -46,7 +64,15 @@ module.exports = {
4664 create : Components . detect ( ( context , components ) => {
4765 const configuration = context . options [ 0 ] || { } ;
4866 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' ;
5076
5177 /**
5278 * Reports all propTypes passed in that don't have a defaultProps counterpart.
@@ -55,14 +81,10 @@ module.exports = {
5581 * @return {void }
5682 */
5783 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 ] ;
6387
64- Object . keys ( propTypes ) . forEach ( ( propName ) => {
65- const prop = propTypes [ propName ] ;
6688 if ( ! prop . node ) {
6789 return ;
6890 }
@@ -87,6 +109,48 @@ module.exports = {
87109 } ) ;
88110 }
89111
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+
90154 // --------------------------------------------------------------------------
91155 // Public API
92156 // --------------------------------------------------------------------------
@@ -95,17 +159,33 @@ module.exports = {
95159 'Program:exit' ( ) {
96160 const list = components . list ( ) ;
97161
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 ) ) {
101164 return false ;
102165 }
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 ;
104176 } ) . 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+ }
109189 } ) ;
110190 } ,
111191 } ;
0 commit comments