Repository: sindresorhus/eslint-plugin-unicorn (JavaScript)

Issue:
consistent-function-scoping breaks down on React HOCs
Given this code:

```js
function withSomething() {
  return function withSomethingFactory(WrappedComponent) {
    function WithSomething(props) {
      // HOC does something cool
      return <WrappedComponent {...props} />;
    }

    return WithSomething;
  };
}
```

The rule fails on line 3. However, `WrappedComponent` is being referenced from the parent as a JSX element.


Discussion:

Patch:
diff --git a/docs/rules/consistent-function-scoping.md b/docs/rules/consistent-function-scoping.md
index dc0f5f6fa2..45ab23fabe 100644
--- a/docs/rules/consistent-function-scoping.md
+++ b/docs/rules/consistent-function-scoping.md
@@ -59,3 +59,15 @@ function doFoo(foo) {
 	return foo;
 }
 ```
+
+It also ignores functions that contain `JSXElement` references:
+
+```jsx
+function doFoo(FooComponent) {
+	function Bar() {
+		return <FooComponent/>;
+	}
+
+	return Bar;
+};
+```
diff --git a/rules/consistent-function-scoping.js b/rules/consistent-function-scoping.js
index c3f9f3a02a..56318861aa 100644
--- a/rules/consistent-function-scoping.js
+++ b/rules/consistent-function-scoping.js
@@ -121,30 +121,48 @@ const create = context => {
 	const sourceCode = context.getSourceCode();
 	const {scopeManager} = sourceCode;
 
+	const reports = [];
+	let hasJsx = false;
+
 	return {
-		ArrowFunctionExpression(node) {
+		ArrowFunctionExpression: node => {
 			const valid = checkNode(node, scopeManager);
 
 			if (valid) {
-				return;
+				reports.push(null);
+			} else {
+				reports.push({
+					node,
+					messageId: MESSAGE_ID_ARROW
+				});
 			}
-
-			context.report({
-				node,
-				messageId: MESSAGE_ID_ARROW
-			});
 		},
-		FunctionDeclaration(node) {
+		FunctionDeclaration: node => {
 			const valid = checkNode(node, scopeManager);
 
 			if (valid) {
-				return;
+				reports.push(null);
+			} else {
+				reports.push({
+					node,
+					messageId: MESSAGE_ID_FUNCTION
+				});
+			}
+		},
+		JSXElement: () => {
+			// Turn off this rule if we see a JSX element because scope
+			// references does not include JSXElement nodes.
+			hasJsx = true;
+		},
+		':matches(ArrowFunctionExpression, FunctionDeclaration):exit': () => {
+			const report = reports.pop();
+			if (report && !hasJsx) {
+				context.report(report);
 			}
 
-			context.report({
-				node,
-				messageId: MESSAGE_ID_FUNCTION
-			});
+			if (reports.length === 0) {
+				hasJsx = false;
+			}
 		}
 	};
 };


Test patch:
diff --git a/test/consistent-function-scoping.js b/test/consistent-function-scoping.js
index 9783bdbb47..ad5eb3cb68 100644
--- a/test/consistent-function-scoping.js
+++ b/test/consistent-function-scoping.js
@@ -6,6 +6,12 @@ import rule from '../rules/consistent-function-scoping';
 const ruleTester = avaRuleTester(test, {
 	env: {
 		es6: true
+	},
+	parserOptions: {
+		ecmaVersion: 2018,
+		ecmaFeatures: {
+			jsx: true
+		}
 	}
 });
 
@@ -156,6 +162,32 @@ ruleTester.run('consistent-function-scoping', rule, {
 
 				doZaz('zaz');
 			};
+		`,
+		outdent`
+			function doFoo() {
+				return function doBar() {};
+			}
+		`,
+		outdent`
+			function doFoo(Foo) {
+				function doBar() {
+					return new Foo();
+				}
+				return doBar;
+			};
+		`,
+		outdent`
+			function doFoo(FooComponent) {
+				return <FooComponent />;
+			}
+		`,
+		outdent`
+			function doFoo(FooComponent) {
+				function Bar() {
+					return <FooComponent />;
+				}
+				return Bar;
+			};
 		`
 	],
 	invalid: [