123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189 |
- /**
- * @fileoverview Prevents usage of Function.prototype.bind and arrow functions
- * in React component props.
- * @author Daniel Lo Nigro <dan.cx>
- * @author Jacky Ho
- */
- 'use strict';
- const propName = require('jsx-ast-utils/propName');
- const Components = require('../util/Components');
- const docsUrl = require('../util/docsUrl');
- const jsxUtil = require('../util/jsx');
- // -----------------------------------------------------------------------------
- // Rule Definition
- // -----------------------------------------------------------------------------
- module.exports = {
- meta: {
- docs: {
- description: 'Prevents usage of Function.prototype.bind and arrow functions in React component props',
- category: 'Best Practices',
- recommended: false,
- url: docsUrl('jsx-no-bind')
- },
- messages: {
- bindCall: 'JSX props should not use .bind()',
- arrowFunc: 'JSX props should not use arrow functions',
- bindExpression: 'JSX props should not use ::',
- func: 'JSX props should not use functions'
- },
- schema: [{
- type: 'object',
- properties: {
- allowArrowFunctions: {
- default: false,
- type: 'boolean'
- },
- allowBind: {
- default: false,
- type: 'boolean'
- },
- allowFunctions: {
- default: false,
- type: 'boolean'
- },
- ignoreRefs: {
- default: false,
- type: 'boolean'
- },
- ignoreDOMComponents: {
- default: false,
- type: 'boolean'
- }
- },
- additionalProperties: false
- }]
- },
- create: Components.detect((context) => {
- const configuration = context.options[0] || {};
- // Keep track of all the variable names pointing to a bind call,
- // bind expression or an arrow function in different block statements
- const blockVariableNameSets = {};
- function setBlockVariableNameSet(blockStart) {
- blockVariableNameSets[blockStart] = {
- arrowFunc: new Set(),
- bindCall: new Set(),
- bindExpression: new Set(),
- func: new Set()
- };
- }
- function getNodeViolationType(node) {
- const nodeType = node.type;
- if (
- !configuration.allowBind
- && nodeType === 'CallExpression'
- && node.callee.type === 'MemberExpression'
- && node.callee.property.type === 'Identifier'
- && node.callee.property.name === 'bind'
- ) {
- return 'bindCall';
- }
- if (nodeType === 'ConditionalExpression') {
- return getNodeViolationType(node.test)
- || getNodeViolationType(node.consequent)
- || getNodeViolationType(node.alternate);
- }
- if (!configuration.allowArrowFunctions && nodeType === 'ArrowFunctionExpression') {
- return 'arrowFunc';
- }
- if (!configuration.allowFunctions && nodeType === 'FunctionExpression') {
- return 'func';
- }
- if (!configuration.allowBind && nodeType === 'BindExpression') {
- return 'bindExpression';
- }
- return null;
- }
- function addVariableNameToSet(violationType, variableName, blockStart) {
- blockVariableNameSets[blockStart][violationType].add(variableName);
- }
- function getBlockStatementAncestors(node) {
- return context.getAncestors(node).reverse().filter(
- (ancestor) => ancestor.type === 'BlockStatement'
- );
- }
- function reportVariableViolation(node, name, blockStart) {
- const blockSets = blockVariableNameSets[blockStart];
- const violationTypes = Object.keys(blockSets);
- return violationTypes.find((type) => {
- if (blockSets[type].has(name)) {
- context.report({
- node,
- messageId: type
- });
- return true;
- }
- return false;
- });
- }
- function findVariableViolation(node, name) {
- getBlockStatementAncestors(node).find(
- (block) => reportVariableViolation(node, name, block.range[0])
- );
- }
- return {
- BlockStatement(node) {
- setBlockVariableNameSet(node.range[0]);
- },
- VariableDeclarator(node) {
- if (!node.init) {
- return;
- }
- const blockAncestors = getBlockStatementAncestors(node);
- const variableViolationType = getNodeViolationType(node.init);
- if (
- blockAncestors.length > 0
- && variableViolationType
- && node.parent.kind === 'const' // only support const right now
- ) {
- addVariableNameToSet(
- variableViolationType, node.id.name, blockAncestors[0].range[0]
- );
- }
- },
- JSXAttribute(node) {
- const isRef = configuration.ignoreRefs && propName(node) === 'ref';
- if (isRef || !node.value || !node.value.expression) {
- return;
- }
- const isDOMComponent = jsxUtil.isDOMComponent(node.parent);
- if (configuration.ignoreDOMComponents && isDOMComponent) {
- return;
- }
- const valueNode = node.value.expression;
- const valueNodeType = valueNode.type;
- const nodeViolationType = getNodeViolationType(valueNode);
- if (valueNodeType === 'Identifier') {
- findVariableViolation(node, valueNode.name);
- } else if (nodeViolationType) {
- context.report({
- node,
- messageId: nodeViolationType
- });
- }
- }
- };
- })
- };
|