@@ -86,154 +86,6 @@ export default {
8686 return result ;
8787 } ;
8888 }
89-
90- return {
91- CallExpression : visitCallExpression ,
92- } ;
93-
94- function visitCallExpression ( node ) {
95- const callbackIndex = getReactiveHookCallbackIndex ( node . callee , options ) ;
96- if ( callbackIndex === - 1 ) {
97- // Not a React Hook call that needs deps.
98- return ;
99- }
100- const callback = node . arguments [ callbackIndex ] ;
101- const reactiveHook = node . callee ;
102- const reactiveHookName = getNodeWithoutReactNamespace ( reactiveHook ) . name ;
103- const declaredDependenciesNode = node . arguments [ callbackIndex + 1 ] ;
104- const isEffect = / E f f e c t ( $ | [ ^ a - z ] ) / g. test ( reactiveHookName ) ;
105-
106- // Check the declared dependencies for this reactive hook. If there is no
107- // second argument then the reactive callback will re-run on every render.
108- // So no need to check for dependency inclusion.
109- if ( ! declaredDependenciesNode && ! isEffect ) {
110- // These are only used for optimization.
111- if (
112- reactiveHookName === 'useMemo' ||
113- reactiveHookName === 'useCallback'
114- ) {
115- // TODO: Can this have a suggestion?
116- reportProblem ( {
117- node : reactiveHook ,
118- message :
119- `React Hook ${ reactiveHookName } does nothing when called with ` +
120- `only one argument. Did you forget to pass an array of ` +
121- `dependencies?` ,
122- } ) ;
123- }
124- return ;
125- }
126-
127- switch ( callback . type ) {
128- case 'FunctionExpression' :
129- case 'ArrowFunctionExpression' :
130- visitFunctionWithDependencies (
131- callback ,
132- declaredDependenciesNode ,
133- reactiveHook ,
134- reactiveHookName ,
135- isEffect ,
136- ) ;
137- return ; // Handled
138- case 'Identifier' :
139- if ( ! declaredDependenciesNode ) {
140- // No deps, no problems.
141- return ; // Handled
142- }
143- // The function passed as a callback is not written inline.
144- // But perhaps it's in the dependencies array?
145- if (
146- declaredDependenciesNode . elements &&
147- declaredDependenciesNode . elements . some (
148- el => el && el . type === 'Identifier' && el . name === callback . name ,
149- )
150- ) {
151- // If it's already in the list of deps, we don't care because
152- // this is valid regardless.
153- return ; // Handled
154- }
155- // We'll do our best effort to find it, complain otherwise.
156- const variable = context . getScope ( ) . set . get ( callback . name ) ;
157- if ( variable == null || variable . defs == null ) {
158- // If it's not in scope, we don't care.
159- return ; // Handled
160- }
161- // The function passed as a callback is not written inline.
162- // But it's defined somewhere in the render scope.
163- // We'll do our best effort to find and check it, complain otherwise.
164- const def = variable . defs [ 0 ] ;
165- if ( ! def || ! def . node ) {
166- break ; // Unhandled
167- }
168- if ( def . type !== 'Variable' && def . type !== 'FunctionName' ) {
169- // Parameter or an unusual pattern. Bail out.
170- break ; // Unhandled
171- }
172- switch ( def . node . type ) {
173- case 'FunctionDeclaration' :
174- // useEffect(() => { ... }, []);
175- visitFunctionWithDependencies (
176- def . node ,
177- declaredDependenciesNode ,
178- reactiveHook ,
179- reactiveHookName ,
180- isEffect ,
181- ) ;
182- return ; // Handled
183- case 'VariableDeclarator' :
184- const init = def . node . init ;
185- if ( ! init ) {
186- break ; // Unhandled
187- }
188- switch ( init . type ) {
189- // const effectBody = () => {...};
190- // useEffect(effectBody, []);
191- case 'ArrowFunctionExpression' :
192- case 'FunctionExpression' :
193- // We can inspect this function as if it were inline.
194- visitFunctionWithDependencies (
195- init ,
196- declaredDependenciesNode ,
197- reactiveHook ,
198- reactiveHookName ,
199- isEffect ,
200- ) ;
201- return ; // Handled
202- }
203- break ; // Unhandled
204- }
205- break ; // Unhandled
206- default :
207- // useEffect(generateEffectBody(), []);
208- reportProblem ( {
209- node : reactiveHook ,
210- message :
211- `React Hook ${ reactiveHookName } received a function whose dependencies ` +
212- `are unknown. Pass an inline function instead.` ,
213- } ) ;
214- return ; // Handled
215- }
216-
217- // Something unusual. Fall back to suggesting to add the body itself as a dep.
218- reportProblem ( {
219- node : reactiveHook ,
220- message :
221- `React Hook ${ reactiveHookName } has a missing dependency: '${ callback . name } '. ` +
222- `Either include it or remove the dependency array.` ,
223- suggest : [
224- {
225- desc : `Update the dependencies array to be: [${ callback . name } ]` ,
226- fix ( fixer ) {
227- return fixer . replaceText (
228- declaredDependenciesNode ,
229- `[${ callback . name } ]` ,
230- ) ;
231- } ,
232- } ,
233- ] ,
234- } ) ;
235- }
236-
23789 /**
23890 * Visitor for both function expressions and arrow function expressions.
23991 */
@@ -1251,6 +1103,153 @@ export default {
12511103 ] ,
12521104 } ) ;
12531105 }
1106+
1107+ function visitCallExpression ( node ) {
1108+ const callbackIndex = getReactiveHookCallbackIndex ( node . callee , options ) ;
1109+ if ( callbackIndex === - 1 ) {
1110+ // Not a React Hook call that needs deps.
1111+ return ;
1112+ }
1113+ const callback = node . arguments [ callbackIndex ] ;
1114+ const reactiveHook = node . callee ;
1115+ const reactiveHookName = getNodeWithoutReactNamespace ( reactiveHook ) . name ;
1116+ const declaredDependenciesNode = node . arguments [ callbackIndex + 1 ] ;
1117+ const isEffect = / E f f e c t ( $ | [ ^ a - z ] ) / g. test ( reactiveHookName ) ;
1118+
1119+ // Check the declared dependencies for this reactive hook. If there is no
1120+ // second argument then the reactive callback will re-run on every render.
1121+ // So no need to check for dependency inclusion.
1122+ if ( ! declaredDependenciesNode && ! isEffect ) {
1123+ // These are only used for optimization.
1124+ if (
1125+ reactiveHookName === 'useMemo' ||
1126+ reactiveHookName === 'useCallback'
1127+ ) {
1128+ // TODO: Can this have a suggestion?
1129+ reportProblem ( {
1130+ node : reactiveHook ,
1131+ message :
1132+ `React Hook ${ reactiveHookName } does nothing when called with ` +
1133+ `only one argument. Did you forget to pass an array of ` +
1134+ `dependencies?` ,
1135+ } ) ;
1136+ }
1137+ return ;
1138+ }
1139+
1140+ switch ( callback . type ) {
1141+ case 'FunctionExpression' :
1142+ case 'ArrowFunctionExpression' :
1143+ visitFunctionWithDependencies (
1144+ callback ,
1145+ declaredDependenciesNode ,
1146+ reactiveHook ,
1147+ reactiveHookName ,
1148+ isEffect ,
1149+ ) ;
1150+ return ; // Handled
1151+ case 'Identifier' :
1152+ if ( ! declaredDependenciesNode ) {
1153+ // No deps, no problems.
1154+ return ; // Handled
1155+ }
1156+ // The function passed as a callback is not written inline.
1157+ // But perhaps it's in the dependencies array?
1158+ if (
1159+ declaredDependenciesNode . elements &&
1160+ declaredDependenciesNode . elements . some (
1161+ el => el && el . type === 'Identifier' && el . name === callback . name ,
1162+ )
1163+ ) {
1164+ // If it's already in the list of deps, we don't care because
1165+ // this is valid regardless.
1166+ return ; // Handled
1167+ }
1168+ // We'll do our best effort to find it, complain otherwise.
1169+ const variable = context . getScope ( ) . set . get ( callback . name ) ;
1170+ if ( variable == null || variable . defs == null ) {
1171+ // If it's not in scope, we don't care.
1172+ return ; // Handled
1173+ }
1174+ // The function passed as a callback is not written inline.
1175+ // But it's defined somewhere in the render scope.
1176+ // We'll do our best effort to find and check it, complain otherwise.
1177+ const def = variable . defs [ 0 ] ;
1178+ if ( ! def || ! def . node ) {
1179+ break ; // Unhandled
1180+ }
1181+ if ( def . type !== 'Variable' && def . type !== 'FunctionName' ) {
1182+ // Parameter or an unusual pattern. Bail out.
1183+ break ; // Unhandled
1184+ }
1185+ switch ( def . node . type ) {
1186+ case 'FunctionDeclaration' :
1187+ // useEffect(() => { ... }, []);
1188+ visitFunctionWithDependencies (
1189+ def . node ,
1190+ declaredDependenciesNode ,
1191+ reactiveHook ,
1192+ reactiveHookName ,
1193+ isEffect ,
1194+ ) ;
1195+ return ; // Handled
1196+ case 'VariableDeclarator' :
1197+ const init = def . node . init ;
1198+ if ( ! init ) {
1199+ break ; // Unhandled
1200+ }
1201+ switch ( init . type ) {
1202+ // const effectBody = () => {...};
1203+ // useEffect(effectBody, []);
1204+ case 'ArrowFunctionExpression' :
1205+ case 'FunctionExpression' :
1206+ // We can inspect this function as if it were inline.
1207+ visitFunctionWithDependencies (
1208+ init ,
1209+ declaredDependenciesNode ,
1210+ reactiveHook ,
1211+ reactiveHookName ,
1212+ isEffect ,
1213+ ) ;
1214+ return ; // Handled
1215+ }
1216+ break ; // Unhandled
1217+ }
1218+ break ; // Unhandled
1219+ default :
1220+ // useEffect(generateEffectBody(), []);
1221+ reportProblem ( {
1222+ node : reactiveHook ,
1223+ message :
1224+ `React Hook ${ reactiveHookName } received a function whose dependencies ` +
1225+ `are unknown. Pass an inline function instead.` ,
1226+ } ) ;
1227+ return ; // Handled
1228+ }
1229+
1230+ // Something unusual. Fall back to suggesting to add the body itself as a dep.
1231+ reportProblem ( {
1232+ node : reactiveHook ,
1233+ message :
1234+ `React Hook ${ reactiveHookName } has a missing dependency: '${ callback . name } '. ` +
1235+ `Either include it or remove the dependency array.` ,
1236+ suggest : [
1237+ {
1238+ desc : `Update the dependencies array to be: [${ callback . name } ]` ,
1239+ fix ( fixer ) {
1240+ return fixer . replaceText (
1241+ declaredDependenciesNode ,
1242+ `[${ callback . name } ]` ,
1243+ ) ;
1244+ } ,
1245+ } ,
1246+ ] ,
1247+ } ) ;
1248+ }
1249+
1250+ return {
1251+ CallExpression : visitCallExpression ,
1252+ } ;
12541253 } ,
12551254} ;
12561255
0 commit comments