Repository: yannickcr/eslint-plugin-react
Pull request #2065 (2018-12-02 03:50:11)

Change allowed propWrapperFunctions setting values
This change is adapting the `propWrapperFunctions` setting to allow for
using objects along side the already present strings. This will help
facilitate adding extra attributes to these objects like for exact prop
functions.

This was initially proposed [here](https://github.com/yannickcr/eslint-plugin-react/pull/1547#issuecomment-345545576).


Patch:
diff --git a/README.md b/README.md
index 161c7566..4e000f59 100644
--- a/README.md
+++ b/README.md
@@ -42,10 +42,12 @@ You should also specify settings that will be shared across all the plugin rules
       "version": "15.0", // React version, default to the latest React stable release
       "flowVersion": "0.53" // Flow version
     },
-    "propWrapperFunctions": [ "forbidExtraProps" ] // The names of any functions used to wrap the
-                                                   // propTypes object, e.g. `forbidExtraProps`.
-                                                   // If this isn't set, any propTypes wrapped in
-                                                   // a function will be skipped.
+    "propWrapperFunctions": [
+        // The names of any function used to wrap propTypes, e.g. `forbidExtraProps`. If this isn't set, any propTypes wrapped in a function will be skipped.
+        "forbidExtraProps",
+        {"property": "freeze", "object": "Object"}
+        {"property": "myFavoriteWrapper"}
+    ]
   }
 }
 ```
diff --git a/lib/rules/boolean-prop-naming.js b/lib/rules/boolean-prop-naming.js
index d1be3364..c464e132 100644
--- a/lib/rules/boolean-prop-naming.js
+++ b/lib/rules/boolean-prop-naming.js
@@ -7,6 +7,7 @@
 const Components = require('../util/Components');
 const propsUtil = require('../util/props');
 const docsUrl = require('../util/docsUrl');
+const propWrapperUtil = require('../util/propWrapper');
 
 // ------------------------------------------------------------------------------
 // Rule Definition
@@ -51,7 +52,6 @@ module.exports = {
     const config = context.options[0] || {};
     const rule = config.rule ? new RegExp(config.rule) : null;
     const propTypeNames = config.propTypeNames || ['bool'];
-    const propWrapperFunctions = new Set(context.settings.propWrapperFunctions || []);
 
     // Remembers all Flowtype object definitions
     const objectTypeAnnotations = new Map();
@@ -171,7 +171,7 @@ module.exports = {
         if (!rule || !propsUtil.isPropTypesDeclaration(node)) {
           return;
         }
-        if (node.value && node.value.type === 'CallExpression' && propWrapperFunctions.has(sourceCode.getText(node.value.callee))) {
+        if (node.value && node.value.type === 'CallExpression' && propWrapperUtil.isPropWrapperFunction(context, sourceCode.getText(node.value.callee))) {
           checkPropWrapperArguments(node, node.value.arguments);
         }
         if (node.value && node.value.properties) {
@@ -191,7 +191,7 @@ module.exports = {
           return;
         }
         const right = node.parent.right;
-        if (right.type === 'CallExpression' && propWrapperFunctions.has(sourceCode.getText(right.callee))) {
+        if (right.type === 'CallExpression' && propWrapperUtil.isPropWrapperFunction(context, sourceCode.getText(right.callee))) {
           checkPropWrapperArguments(component.node, right.arguments);
           return;
         }
diff --git a/lib/rules/forbid-prop-types.js b/lib/rules/forbid-prop-types.js
index afb171a3..0d4cb599 100644
--- a/lib/rules/forbid-prop-types.js
+++ b/lib/rules/forbid-prop-types.js
@@ -7,6 +7,7 @@ const variableUtil = require('../util/variable');
 const propsUtil = require('../util/props');
 const astUtil = require('../util/ast');
 const docsUrl = require('../util/docsUrl');
+const propWrapperUtil = require('../util/propWrapper');
 
 // ------------------------------------------------------------------------------
 // Constants
@@ -48,7 +49,6 @@ module.exports = {
   },
 
   create: function(context) {
-    const propWrapperFunctions = new Set(context.settings.propWrapperFunctions || []);
     const configuration = context.options[0] || {};
     const checkContextTypes = configuration.checkContextTypes || false;
     const checkChildContextTypes = configuration.checkChildContextTypes || false;
@@ -125,7 +125,7 @@ module.exports = {
           break;
         case 'CallExpression':
           const innerNode = node.arguments && node.arguments[0];
-          if (propWrapperFunctions.has(node.callee.name) && innerNode) {
+          if (propWrapperUtil.isPropWrapperFunction(context, node.callee.name) && innerNode) {
             checkNode(innerNode);
           }
           break;
diff --git a/lib/rules/jsx-sort-default-props.js b/lib/rules/jsx-sort-default-props.js
index cb917dd0..ca9b0fc7 100644
--- a/lib/rules/jsx-sort-default-props.js
+++ b/lib/rules/jsx-sort-default-props.js
@@ -6,6 +6,7 @@
 
 const variableUtil = require('../util/variable');
 const docsUrl = require('../util/docsUrl');
+const propWrapperUtil = require('../util/propWrapper');
 
 // ------------------------------------------------------------------------------
 // Rule Definition
@@ -35,7 +36,6 @@ module.exports = {
     const sourceCode = context.getSourceCode();
     const configuration = context.options[0] || {};
     const ignoreCase = configuration.ignoreCase || false;
-    const propWrapperFunctions = new Set(context.settings.propWrapperFunctions || []);
 
     /**
      * Get properties name
@@ -134,7 +134,7 @@ module.exports = {
           break;
         case 'CallExpression':
           const innerNode = node.arguments && node.arguments[0];
-          if (propWrapperFunctions.has(node.callee.name) && innerNode) {
+          if (propWrapperUtil.isPropWrapperFunction(context, node.callee.name) && innerNode) {
             checkNode(innerNode);
           }
           break;
diff --git a/lib/rules/sort-prop-types.js b/lib/rules/sort-prop-types.js
index a2e2cf22..676785c0 100644
--- a/lib/rules/sort-prop-types.js
+++ b/lib/rules/sort-prop-types.js
@@ -6,6 +6,7 @@
 const variableUtil = require('../util/variable');
 const propsUtil = require('../util/props');
 const docsUrl = require('../util/docsUrl');
+const propWrapperUtil = require('../util/propWrapper');
 
 // ------------------------------------------------------------------------------
 // Rule Definition
@@ -54,7 +55,6 @@ module.exports = {
     const ignoreCase = configuration.ignoreCase || false;
     const noSortAlphabetically = configuration.noSortAlphabetically || false;
     const sortShapeProp = configuration.sortShapeProp || false;
-    const propWrapperFunctions = new Set(context.settings.propWrapperFunctions || []);
 
     function getKey(node) {
       if (node.key && node.key.value) {
@@ -250,7 +250,7 @@ module.exports = {
           break;
         case 'CallExpression':
           const innerNode = node.arguments && node.arguments[0];
-          if (propWrapperFunctions.has(node.callee.name) && innerNode) {
+          if (propWrapperUtil.isPropWrapperFunction(context, node.callee.name) && innerNode) {
             checkNode(innerNode);
           }
           break;
diff --git a/lib/util/defaultProps.js b/lib/util/defaultProps.js
index 32b0aec0..4b39c61c 100644
--- a/lib/util/defaultProps.js
+++ b/lib/util/defaultProps.js
@@ -7,12 +7,12 @@ const fromEntries = require('object.fromentries');
 const astUtil = require('./ast');
 const propsUtil = require('./props');
 const variableUtil = require('./variable');
+const propWrapperUtil = require('../util/propWrapper');
 
 const QUOTES_REGEX = /^["']|["']$/g;
 
 module.exports = function defaultPropsInstructions(context, components, utils) {
   const sourceCode = context.getSourceCode();
-  const propWrapperFunctions = new Set(context.settings.propWrapperFunctions || []);
 
   /**
    * Try to resolve the node passed in to a variable in the current scope. If the node passed in is not
@@ -26,7 +26,7 @@ module.exports = function defaultPropsInstructions(context, components, utils) {
     }
     if (
       node.type === 'CallExpression' &&
-      propWrapperFunctions.has(node.callee.name) &&
+      propWrapperUtil.isPropWrapperFunction(context, node.callee.name) &&
       node.arguments && node.arguments[0]
     ) {
       return resolveNodeValue(node.arguments[0]);
diff --git a/lib/util/propTypes.js b/lib/util/propTypes.js
index d90f7d77..5d4c3de7 100644
--- a/lib/util/propTypes.js
+++ b/lib/util/propTypes.js
@@ -7,6 +7,7 @@ const annotations = require('./annotations');
 const propsUtil = require('./props');
 const variableUtil = require('./variable');
 const versionUtil = require('./version');
+const propWrapperUtil = require('../util/propWrapper');
 
 /**
  * Checks if we are declaring a props as a generic type in a flow-annotated class.
@@ -76,7 +77,6 @@ module.exports = function propTypesInstructions(context, components, utils) {
   const configuration = Object.assign({}, defaults, context.options[0] || {});
   const customValidators = configuration.customValidators;
   const sourceCode = context.getSourceCode();
-  const propWrapperFunctions = new Set(context.settings.propWrapperFunctions);
 
   /**
    * Returns the full scope.
@@ -522,7 +522,7 @@ module.exports = function propTypesInstructions(context, components, utils) {
         break;
       case 'CallExpression':
         if (
-          propWrapperFunctions.has(sourceCode.getText(propTypes.callee)) &&
+          propWrapperUtil.isPropWrapperFunction(context, sourceCode.getText(propTypes.callee)) &&
           propTypes.arguments && propTypes.arguments[0]
         ) {
           markPropTypesAsDeclared(node, propTypes.arguments[0]);
diff --git a/lib/util/propWrapper.js b/lib/util/propWrapper.js
new file mode 100644
index 00000000..b41dd093
--- /dev/null
+++ b/lib/util/propWrapper.js
@@ -0,0 +1,24 @@
+/**
+ * @fileoverview Utility functions for propWrapperFunctions setting
+ */
+'use strict';
+
+function getPropWrapperFunctions(context) {
+  return new Set(context.settings.propWrapperFunctions || []);
+}
+
+function isPropWrapperFunction(context, name) {
+  const propWrapperFunctions = getPropWrapperFunctions(context);
+  const splitName = name.split('.');
+  return Array.from(propWrapperFunctions).some(func => {
+    if (splitName.length === 2 && func.object === splitName[0] && func.property === splitName[1]) {
+      return true;
+    }
+    return name === func || func.property === name;
+  });
+}
+
+module.exports = {
+  getPropWrapperFunctions: getPropWrapperFunctions,
+  isPropWrapperFunction: isPropWrapperFunction
+};


Test patch:
diff --git a/tests/util/propWrapper.js b/tests/util/propWrapper.js
new file mode 100644
index 00000000..6cbef977
--- /dev/null
+++ b/tests/util/propWrapper.js
@@ -0,0 +1,62 @@
+/* eslint-env mocha */
+'use strict';
+
+const assert = require('assert');
+const propWrapperUtil = require('../../lib/util/propWrapper');
+
+describe('PropWrapperFunctions', () => {
+  describe('getPropWrapperFunctions', () => {
+    it('returns set of functions if setting exists', () => {
+      const propWrapperFunctions = ['Object.freeze', {
+        property: 'forbidExtraProps'
+      }];
+      const context = {
+        settings: {
+          propWrapperFunctions: propWrapperFunctions
+        }
+      };
+      assert.deepStrictEqual(propWrapperUtil.getPropWrapperFunctions(context), new Set(propWrapperFunctions));
+    });
+
+    it('returns empty array if no setting', () => {
+      const context = {
+        settings: {}
+      };
+      assert.deepStrictEqual(propWrapperUtil.getPropWrapperFunctions(context), new Set([]));
+    });
+  });
+
+  describe('isPropWrapperFunction', () => {
+    it('with string', () => {
+      const context = {
+        settings: {
+          propWrapperFunctions: ['Object.freeze']
+        }
+      };
+      assert.equal(propWrapperUtil.isPropWrapperFunction(context, 'Object.freeze'), true);
+    });
+
+    it('with Object with object and property keys', () => {
+      const context = {
+        settings: {
+          propWrapperFunctions: [{
+            property: 'freeze',
+            object: 'Object'
+          }]
+        }
+      };
+      assert.equal(propWrapperUtil.isPropWrapperFunction(context, 'Object.freeze'), true);
+    });
+
+    it('with Object with only property key', () => {
+      const context = {
+        settings: {
+          propWrapperFunctions: [{
+            property: 'forbidExtraProps'
+          }]
+        }
+      };
+      assert.equal(propWrapperUtil.isPropWrapperFunction(context, 'forbidExtraProps'), true);
+    });
+  });
+});