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