UNPKG

5.44 kBJavaScriptView Raw
1/**
2 * @fileoverview Prevents usage of Function.prototype.bind and arrow functions
3 * in React component props.
4 * @author Daniel Lo Nigro <dan.cx>
5 * @author Jacky Ho
6 */
7
8'use strict';
9
10const propName = require('jsx-ast-utils/propName');
11const Components = require('../util/Components');
12const docsUrl = require('../util/docsUrl');
13const jsxUtil = require('../util/jsx');
14
15// -----------------------------------------------------------------------------
16// Rule Definition
17// -----------------------------------------------------------------------------
18
19const violationMessageStore = {
20 bindCall: 'JSX props should not use .bind()',
21 arrowFunc: 'JSX props should not use arrow functions',
22 bindExpression: 'JSX props should not use ::',
23 func: 'JSX props should not use functions'
24};
25
26module.exports = {
27 meta: {
28 docs: {
29 description: 'Prevents usage of Function.prototype.bind and arrow functions in React component props',
30 category: 'Best Practices',
31 recommended: false,
32 url: docsUrl('jsx-no-bind')
33 },
34
35 schema: [{
36 type: 'object',
37 properties: {
38 allowArrowFunctions: {
39 default: false,
40 type: 'boolean'
41 },
42 allowBind: {
43 default: false,
44 type: 'boolean'
45 },
46 allowFunctions: {
47 default: false,
48 type: 'boolean'
49 },
50 ignoreRefs: {
51 default: false,
52 type: 'boolean'
53 },
54 ignoreDOMComponents: {
55 default: false,
56 type: 'boolean'
57 }
58 },
59 additionalProperties: false
60 }]
61 },
62
63 create: Components.detect((context) => {
64 const configuration = context.options[0] || {};
65
66 // Keep track of all the variable names pointing to a bind call,
67 // bind expression or an arrow function in different block statements
68 const blockVariableNameSets = {};
69
70 function setBlockVariableNameSet(blockStart) {
71 blockVariableNameSets[blockStart] = {
72 arrowFunc: new Set(),
73 bindCall: new Set(),
74 bindExpression: new Set(),
75 func: new Set()
76 };
77 }
78
79 function getNodeViolationType(node) {
80 const nodeType = node.type;
81
82 if (
83 !configuration.allowBind
84 && nodeType === 'CallExpression'
85 && node.callee.type === 'MemberExpression'
86 && node.callee.property.type === 'Identifier'
87 && node.callee.property.name === 'bind'
88 ) {
89 return 'bindCall';
90 }
91 if (nodeType === 'ConditionalExpression') {
92 return getNodeViolationType(node.test)
93 || getNodeViolationType(node.consequent)
94 || getNodeViolationType(node.alternate);
95 }
96 if (!configuration.allowArrowFunctions && nodeType === 'ArrowFunctionExpression') {
97 return 'arrowFunc';
98 }
99 if (!configuration.allowFunctions && nodeType === 'FunctionExpression') {
100 return 'func';
101 }
102 if (!configuration.allowBind && nodeType === 'BindExpression') {
103 return 'bindExpression';
104 }
105
106 return null;
107 }
108
109 function addVariableNameToSet(violationType, variableName, blockStart) {
110 blockVariableNameSets[blockStart][violationType].add(variableName);
111 }
112
113 function getBlockStatementAncestors(node) {
114 return context.getAncestors(node).reverse().filter(
115 (ancestor) => ancestor.type === 'BlockStatement'
116 );
117 }
118
119 function reportVariableViolation(node, name, blockStart) {
120 const blockSets = blockVariableNameSets[blockStart];
121 const violationTypes = Object.keys(blockSets);
122
123 return violationTypes.find((type) => {
124 if (blockSets[type].has(name)) {
125 context.report({node, message: violationMessageStore[type]});
126 return true;
127 }
128
129 return false;
130 });
131 }
132
133 function findVariableViolation(node, name) {
134 getBlockStatementAncestors(node).find(
135 (block) => reportVariableViolation(node, name, block.range[0])
136 );
137 }
138
139 return {
140 BlockStatement(node) {
141 setBlockVariableNameSet(node.range[0]);
142 },
143
144 VariableDeclarator(node) {
145 if (!node.init) {
146 return;
147 }
148 const blockAncestors = getBlockStatementAncestors(node);
149 const variableViolationType = getNodeViolationType(node.init);
150
151 if (
152 blockAncestors.length > 0
153 && variableViolationType
154 && node.parent.kind === 'const' // only support const right now
155 ) {
156 addVariableNameToSet(
157 variableViolationType, node.id.name, blockAncestors[0].range[0]
158 );
159 }
160 },
161
162 JSXAttribute(node) {
163 const isRef = configuration.ignoreRefs && propName(node) === 'ref';
164 if (isRef || !node.value || !node.value.expression) {
165 return;
166 }
167 const isDOMComponent = jsxUtil.isDOMComponent(node.parent);
168 if (configuration.ignoreDOMComponents && isDOMComponent) {
169 return;
170 }
171 const valueNode = node.value.expression;
172 const valueNodeType = valueNode.type;
173 const nodeViolationType = getNodeViolationType(valueNode);
174
175 if (valueNodeType === 'Identifier') {
176 findVariableViolation(node, valueNode.name);
177 } else if (nodeViolationType) {
178 context.report({
179 node, message: violationMessageStore[nodeViolationType]
180 });
181 }
182 }
183 };
184 })
185};