1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 | 'use strict';
|
9 |
|
10 | const propName = require('jsx-ast-utils/propName');
|
11 | const Components = require('../util/Components');
|
12 | const docsUrl = require('../util/docsUrl');
|
13 | const jsxUtil = require('../util/jsx');
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 | const 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 |
|
26 | module.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 |
|
67 |
|
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'
|
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 | };
|