diff --git a/lib/util/Components.js b/lib/util/Components.js
index 04d36be260..264a34fcb9 100644
--- a/lib/util/Components.js
+++ b/lib/util/Components.js
@@ -7,6 +7,7 @@
const doctrine = require('doctrine');
const arrayIncludes = require('array-includes');
+const values = require('object.values');
const variableUtil = require('./variable');
const pragmaUtil = require('./pragma');
@@ -437,6 +438,75 @@ function componentRule(rule, context) {
return prevNode;
},
+ getComponentNameFromJSXElement(node) {
+ if (node.type !== 'JSXElement') {
+ return null;
+ }
+ if (node.openingElement && node.openingElement.name && node.openingElement.name.name) {
+ return node.openingElement.name.name;
+ }
+ return null;
+ },
+
+ /**
+ *
+ * @param {object} node
+ * Getting the first JSX element's name.
+ */
+ getNameOfWrappedComponent(node) {
+ if (node.length < 1) {
+ return null;
+ }
+ const body = node[0].body;
+ if (!body) {
+ return null;
+ }
+ if (body.type === 'JSXElement') {
+ return this.getComponentNameFromJSXElement(body);
+ }
+ if (body.type === 'BlockStatement') {
+ const jsxElement = body.body.find((item) => item.type === 'ReturnStatement');
+ return jsxElement && this.getComponentNameFromJSXElement(jsxElement.argument);
+ }
+ return null;
+ },
+
+ /**
+ * Get the list of names of components created till now
+ */
+ getDetectedComponents() {
+ const list = components.list();
+ return values(list).filter((val) => {
+ if (val.node.type === 'ClassDeclaration') {
+ return true;
+ }
+ if (
+ val.node.type === 'ArrowFunctionExpression' &&
+ val.node.parent &&
+ val.node.parent.type === 'VariableDeclarator' &&
+ val.node.parent.id
+ ) {
+ return true;
+ }
+ return false;
+ }).map((val) => {
+ if (val.node.type === 'ArrowFunctionExpression') return val.node.parent.id.name;
+ return val.node.id.name;
+ });
+ },
+
+ /**
+ *
+ * @param {object} node
+ * It will check wheater memo/forwardRef is wrapping existing component or
+ * creating a new one.
+ */
+ nodeWrapsComponent(node) {
+ const childComponent = this.getNameOfWrappedComponent(node.arguments);
+ const componentList = this.getDetectedComponents();
+ return childComponent && arrayIncludes(componentList, childComponent);
+ },
+
isPragmaComponentWrapper(node) {
if (!node || node.type !== 'CallExpression') {
return false;
@@ -444,7 +514,9 @@ function componentRule(rule, context) {
const propertyNames = ['forwardRef', 'memo'];
const calleeObject = node.callee.object;
if (calleeObject && node.callee.property) {
- return arrayIncludes(propertyNames, node.callee.property.name) && calleeObject.name === pragma;
+ return arrayIncludes(propertyNames, node.callee.property.name) &&
+ calleeObject.name === pragma &&
+ !this.nodeWrapsComponent(node);
}
return arrayIncludes(propertyNames, node.callee.name) && this.isDestructuredFromPragmaImport(node.callee.name);
},
diff --git a/tests/lib/rules/no-multi-comp.js b/tests/lib/rules/no-multi-comp.js
index 9728276c80..eafbbf554b 100644
--- a/tests/lib/rules/no-multi-comp.js
+++ b/tests/lib/rules/no-multi-comp.js
@@ -116,6 +116,111 @@ ruleTester.run('no-multi-comp', rule, {
options: [{
ignoreStateless: true
}]
+ }, {
+ code: `
+ class StoreListItem extends React.PureComponent {
+ // A bunch of stuff here
+ }
+ export default React.forwardRef((props, ref) =>