You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
185 lines
5.3 KiB
185 lines
5.3 KiB
/** |
|
* @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 |
|
// ----------------------------------------------------------------------------- |
|
|
|
const violationMessageStore = { |
|
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' |
|
}; |
|
|
|
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') |
|
}, |
|
|
|
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, message: violationMessageStore[type]}); |
|
return true; |
|
} |
|
|
|
return false; |
|
}); |
|
} |
|
|
|
function findVariableViolation(node, name) { |
|
getBlockStatementAncestors(node).find( |
|
block => reportVariableViolation(node, name, block.start) |
|
); |
|
} |
|
|
|
return { |
|
BlockStatement(node) { |
|
setBlockVariableNameSet(node.start); |
|
}, |
|
|
|
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].start |
|
); |
|
} |
|
}, |
|
|
|
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, message: violationMessageStore[nodeViolationType] |
|
}); |
|
} |
|
} |
|
}; |
|
}) |
|
};
|
|
|