@samitouri / QOS-React-2 / commits / 53e622ca7f

Fix instances of function declaration after return (#19733)

* Add ESLint plugin to check for any function declare after return * Refactor code to move function declarations before return and fix failing lint

Bhumij Gupta committed Sep 1, 2020 at 18:25 UTC 53e622ca7f643cc9d18c9c4896b68ab14549e292
6 files changed +170 -158
.eslintrc.js
+10 -1
@@ -16,7 +16,13 @@ module.exports = {
16 // Stop ESLint from looking for a configuration file in parent folders
17 root: true,
18
19 - plugins: ['jest', 'no-for-of-loops', 'react', 'react-internal'],
19 + plugins: [
20 + 'jest',
21 + 'no-for-of-loops',
22 + 'no-function-declare-after-return',
23 + 'react',
24 + 'react-internal',
25 + ],
26
27 parser: 'babel-eslint',
28 parserOptions: {
@@ -91,6 +97,9 @@ module.exports = {
97 // You can disable this rule for code that isn't shipped (e.g. build scripts and tests).
98 'no-for-of-loops/no-for-of-loops': ERROR,
99
100 + // Prevent function declarations after return statements
101 + 'no-function-declare-after-return/no-function-declare-after-return': ERROR,
102 +
103 // CUSTOM RULES
104 // the second argument of warning/invariant should be a literal string
105 'react-internal/no-primitive-constructors': ERROR,
package.json
+1
@@ -54,6 +54,7 @@
54 "eslint-plugin-flowtype": "^2.25.0",
55 "eslint-plugin-jest": "^22.15.0",
56 "eslint-plugin-no-for-of-loops": "^1.0.0",
57 + "eslint-plugin-no-function-declare-after-return": "^1.0.0",
58 "eslint-plugin-react": "^6.7.1",
59 "eslint-plugin-react-internal": "link:./scripts/eslint-rules",
60 "fbjs-scripts": "1.2.0",
packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js
+147 -148
@@ -86,154 +86,6 @@ export default {
86 return result;
87 };
88 }
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 = /Effect($|[^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 -
89 /**
90 * Visitor for both function expressions and arrow function expressions.
91 */
@@ -1251,6 +1103,153 @@ export default {
1103 ],
1104 });
1105 }
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 = /Effect($|[^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 + };
1253 },
1254 };
1255
packages/react-native-renderer/src/__mocks__/react-native/Libraries/ReactPrivate/UIManager.js
+1 -2
@@ -60,8 +60,6 @@ function removeChild(parent, child) {
60
61 const RCTUIManager = {
62 __dumpHierarchyForJestTestsOnly: function() {
63 - return roots.map(tag => dumpSubtree(tag, 0)).join('\n');
64 -
63 function dumpSubtree(tag, indent) {
64 const info = views.get(tag);
65 let out = '';
@@ -73,6 +71,7 @@ const RCTUIManager = {
71 }
72 return out;
73 }
74 + return roots.map(tag => dumpSubtree(tag, 0)).join('\n');
75 },
76 clearJSResponder: jest.fn(),
77 createView: jest.fn(function createView(reactTag, viewName, rootTag, props) {
packages/react-native-renderer/src/legacy-events/SyntheticEvent.js
+6 -7
@@ -259,13 +259,6 @@ addEventPoolingTo(SyntheticEvent);
259 * @return {object} defineProperty object
260 */
261 function getPooledWarningPropertyDefinition(propName, getVal) {
262 - const isFunction = typeof getVal === 'function';
263 - return {
264 - configurable: true,
265 - set: set,
266 - get: get,
267 - };
268 -
262 function set(val) {
263 const action = isFunction ? 'setting the method' : 'setting the property';
264 warn(action, 'This is effectively a no-op');
@@ -296,6 +289,12 @@ function getPooledWarningPropertyDefinition(propName, getVal) {
289 );
290 }
291 }
292 + const isFunction = typeof getVal === 'function';
293 + return {
294 + configurable: true,
295 + set: set,
296 + get: get,
297 + };
298 }
299
300 function createOrGetPooledEvent(
yarn.lock
+5
@@ -5407,6 +5407,11 @@ eslint-plugin-no-for-of-loops@^1.0.0:
5407 resolved "https://registry.yarnpkg.com/eslint-plugin-no-for-of-loops/-/eslint-plugin-no-for-of-loops-1.0.1.tgz#2ee3732d50c642b8d1bfacedbfe3b28f86222369"
5408 integrity sha512-uCotzBHt2W+HbLw2srRmqDJHOPbJGzeVLstKh8YyxS3ppduq2P50qdpJfHKoD+UGbnqA/zhy8NRgPH6p0y8bnA==
5409
5410 +eslint-plugin-no-function-declare-after-return@^1.0.0:
5411 + version "1.0.0"
5412 + resolved "https://registry.yarnpkg.com/eslint-plugin-no-function-declare-after-return/-/eslint-plugin-no-function-declare-after-return-1.0.0.tgz#ddf01d71d27b37ced61ccb295c6ac0708d87b209"
5413 + integrity sha512-/w6tuSK4kYSwHSlPtf36u/rQOG8YVPgl8a0bh4rV/ElZNaUGYOquY/hp4jaCnEmPta0aTpAkFEmSi6ZTd7wCEQ==
5414 +
5415 eslint-plugin-no-unsanitized@3.1.2:
5416 version "3.1.2"
5417 resolved "https://registry.yarnpkg.com/eslint-plugin-no-unsanitized/-/eslint-plugin-no-unsanitized-3.1.2.tgz#a54724e0b81d43279bb1f8f5e1d82c97da78c858"