From a33bfb18ca515e824c5ade55aab1505877b4501e Mon Sep 17 00:00:00 2001 From: Joshua Stiefer Date: Sat, 1 Dec 2018 20:45:33 -0700 Subject: [PATCH] [New] 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. Precursor to #1547. --- README.md | 10 +++-- lib/rules/boolean-prop-naming.js | 6 +-- lib/rules/forbid-prop-types.js | 4 +- lib/rules/jsx-sort-default-props.js | 4 +- lib/rules/sort-prop-types.js | 4 +- lib/util/defaultProps.js | 4 +- lib/util/propTypes.js | 4 +- lib/util/propWrapper.js | 24 +++++++++++ tests/util/propWrapper.js | 62 +++++++++++++++++++++++++++++ 9 files changed, 105 insertions(+), 17 deletions(-) create mode 100644 lib/util/propWrapper.js create mode 100644 tests/util/propWrapper.js diff --git a/README.md b/README.md index 161c75663b..4e000f5989 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 d1be3364aa..c464e13265 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 afb171a348..0d4cb599ff 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 cb917dd0ac..ca9b0fc714 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 a2e2cf22a5..676785c061 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 32b0aec051..4b39c61c43 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 d90f7d7713..5d4c3de7f1 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 0000000000..b41dd0933c --- /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 +}; diff --git a/tests/util/propWrapper.js b/tests/util/propWrapper.js new file mode 100644 index 0000000000..6cbef977ff --- /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); + }); + }); +});