Skip to content

Commit 5709271

Browse files
alexzherdevljharb
authored andcommitted
[New] Add ignoreDOMComponents option to jsx-no-bind
Resolves jsx-eslint#1238
1 parent c3e57c7 commit 5709271

File tree

9 files changed

+96
-56
lines changed

9 files changed

+96
-56
lines changed

docs/rules/jsx-no-bind.md

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,59 +7,70 @@ A `bind` call or [arrow function](https://developer.mozilla.org/en-US/docs/Web/J
77
The following patterns are considered warnings:
88

99
```jsx
10-
<div onClick={this._handleClick.bind(this)}></div>
10+
<Foo onClick={this._handleClick.bind(this)}></Foo>
1111
```
1212
```jsx
13-
<div onClick={() => console.log('Hello!')}></div>
13+
<Foo onClick={() => console.log('Hello!')}></Foo>
1414
```
1515

1616
The following patterns are **not** considered warnings:
1717
```jsx
18-
<div onClick={this._handleClick}></div>
18+
<Foo onClick={this._handleClick}></Foo>
1919
```
2020

2121
## Rule Options
2222

2323
```js
2424
"react/jsx-no-bind": [<enabled>, {
25+
"ignoreDOMComponents": <boolean> || false,
2526
"ignoreRefs": <boolean> || false,
2627
"allowArrowFunctions": <boolean> || false,
2728
"allowFunctions": <boolean> || false,
2829
"allowBind": <boolean> || false
2930
}]
3031
```
3132

33+
### `ignoreDOMComponents`
34+
35+
When `true` the following are **not** considered warnings:
36+
37+
```jsx
38+
<div onClick={this._handleClick.bind(this) />
39+
<span onClick={() => console.log("Hello!")} />
40+
<button onClick={function() { alert("1337") }} />
41+
```
42+
3243
### `ignoreRefs`
3344
3445
When `true` the following are **not** considered warnings:
3546
3647
```jsx
37-
<div ref={c => this._div = c} />
38-
<div ref={this._refCallback.bind(this)} />
48+
<Foo ref={c => this._div = c} />
49+
<Foo ref={this._refCallback.bind(this)} />
3950
```
4051
4152
### `allowArrowFunctions`
4253
4354
When `true` the following is **not** considered a warning:
4455
4556
```jsx
46-
<div onClick={() => alert("1337")} />
57+
<Foo onClick={() => alert("1337")} />
4758
```
4859
4960
### `allowFunctions`
5061
5162
When `true` the following is not considered a warning:
5263
5364
```jsx
54-
<div onClick={function () { alert("1337") }} />
65+
<Foo onClick={function () { alert("1337") }} />
5566
```
5667
5768
### `allowBind`
5869
5970
When `true` the following is **not** considered a warning:
6071
6172
```jsx
62-
<div onClick={this._handleClick.bind(this)} />
73+
<Foo onClick={this._handleClick.bind(this)} />
6374
```
6475
6576
## Protips

lib/rules/jsx-no-bind.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
const propName = require('jsx-ast-utils/propName');
1010
const Components = require('../util/Components');
1111
const docsUrl = require('../util/docsUrl');
12+
const jsxUtil = require('../util/jsx');
1213

1314
// -----------------------------------------------------------------------------
1415
// Rule Definition
@@ -48,6 +49,10 @@ module.exports = {
4849
ignoreRefs: {
4950
default: false,
5051
type: 'boolean'
52+
},
53+
ignoreDOMComponents: {
54+
default: false,
55+
type: 'boolean'
5156
}
5257
},
5358
additionalProperties: false
@@ -165,6 +170,10 @@ module.exports = {
165170
if (isRef || !node.value || !node.value.expression) {
166171
return;
167172
}
173+
const isDOMComponent = jsxUtil.isDOMComponent(node.parent);
174+
if (configuration.ignoreDOMComponents && isDOMComponent) {
175+
return;
176+
}
168177
const valueNode = node.value.expression;
169178
const valueNodeType = valueNode.type;
170179
const nodeViolationType = getNodeViolationType(valueNode);

lib/rules/jsx-no-undef.js

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,7 @@
66
'use strict';
77

88
const docsUrl = require('../util/docsUrl');
9-
10-
/**
11-
* Checks if a node name match the JSX tag convention.
12-
* @param {String} name - Name of the node to check.
13-
* @returns {boolean} Whether or not the node name match the JSX tag convention.
14-
*/
15-
const tagConvention = /^[a-z]|\-/;
16-
function isTagName(name) {
17-
return tagConvention.test(name);
18-
}
9+
const jsxUtil = require('../util/jsx');
1910

2011
// ------------------------------------------------------------------------------
2112
// Rule Definition
@@ -95,10 +86,10 @@ module.exports = {
9586
JSXOpeningElement: function(node) {
9687
switch (node.name.type) {
9788
case 'JSXIdentifier':
98-
node = node.name;
99-
if (isTagName(node.name)) {
89+
if (jsxUtil.isDOMComponent(node)) {
10090
return;
10191
}
92+
node = node.name;
10293
break;
10394
case 'JSXMemberExpression':
10495
node = node.name;

lib/rules/jsx-pascal-case.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@
77

88
const elementType = require('jsx-ast-utils/elementType');
99
const docsUrl = require('../util/docsUrl');
10+
const jsxUtil = require('../util/jsx');
1011

1112
// ------------------------------------------------------------------------------
1213
// Constants
1314
// ------------------------------------------------------------------------------
1415

1516
const PASCAL_CASE_REGEX = /^([A-Z0-9]|[A-Z0-9]+[a-z0-9]+(?:[A-Z0-9]+[a-z0-9]*)*)$/;
16-
const COMPAT_TAG_REGEX = /^[a-z]|\-/;
1717
const ALL_CAPS_TAG_REGEX = /^[A-Z0-9]+$/;
1818

1919
// ------------------------------------------------------------------------------
@@ -60,7 +60,7 @@ module.exports = {
6060
}
6161

6262
const isPascalCase = PASCAL_CASE_REGEX.test(name);
63-
const isCompatTag = COMPAT_TAG_REGEX.test(name);
63+
const isCompatTag = jsxUtil.isDOMComponent(node);
6464
const isAllowedAllCaps = allowAllCaps && ALL_CAPS_TAG_REGEX.test(name);
6565
const isIgnored = ignore.indexOf(name) !== -1;
6666

lib/rules/jsx-sort-props.js

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
*/
55
'use strict';
66

7-
const elementType = require('jsx-ast-utils/elementType');
87
const propName = require('jsx-ast-utils/propName');
98
const docsUrl = require('../util/docsUrl');
9+
const jsxUtil = require('../util/jsx');
1010

1111
// ------------------------------------------------------------------------------
1212
// Rule Definition
@@ -16,20 +16,6 @@ function isCallbackPropName(name) {
1616
return /^on[A-Z]/.test(name);
1717
}
1818

19-
const COMPAT_TAG_REGEX = /^[a-z]|\-/;
20-
function isDOMComponent(node) {
21-
let name = elementType(node);
22-
23-
// Get namespace if the type is JSXNamespacedName or JSXMemberExpression
24-
if (name.indexOf(':') > -1) {
25-
name = name.substring(0, name.indexOf(':'));
26-
} else if (name.indexOf('.') > -1) {
27-
name = name.substring(0, name.indexOf('.'));
28-
}
29-
30-
return COMPAT_TAG_REGEX.test(name);
31-
}
32-
3319
const RESERVED_PROPS_LIST = [
3420
'children',
3521
'dangerouslySetInnerHTML',
@@ -229,7 +215,7 @@ module.exports = {
229215
return {
230216
JSXOpeningElement: function(node) {
231217
// `dangerouslySetInnerHTML` is only "reserved" on DOM components
232-
if (reservedFirst && !isDOMComponent(node)) {
218+
if (reservedFirst && !jsxUtil.isDOMComponent(node)) {
233219
reservedList = reservedList.filter(prop => prop !== 'dangerouslySetInnerHTML');
234220
}
235221

lib/rules/no-danger.js

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
'use strict';
66

77
const docsUrl = require('../util/docsUrl');
8+
const jsxUtil = require('../util/jsx');
89

910
// ------------------------------------------------------------------------------
1011
// Constants
@@ -25,16 +26,6 @@ const DANGEROUS_PROPERTIES = DANGEROUS_PROPERTY_NAMES.reduce((props, prop) => {
2526
// Helpers
2627
// ------------------------------------------------------------------------------
2728

28-
/**
29-
* Checks if a node name match the JSX tag convention.
30-
* @param {String} name - Name of the node to check.
31-
* @returns {boolean} Whether or not the node name match the JSX tag convention.
32-
*/
33-
const tagConvention = /^[a-z]|\-/;
34-
function isTagName(name) {
35-
return tagConvention.test(name);
36-
}
37-
3829
/**
3930
* Checks if a JSX attribute is dangerous.
4031
* @param {String} name - Name of the attribute to check.
@@ -63,7 +54,7 @@ module.exports = {
6354
return {
6455

6556
JSXAttribute: function(node) {
66-
if (isTagName(node.parent.name.name) && isDangerous(node.name.name)) {
57+
if (jsxUtil.isDOMComponent(node.parent) && isDangerous(node.name.name)) {
6758
context.report({
6859
node: node,
6960
message: DANGEROUS_MESSAGE,

lib/rules/self-closing-comp.js

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
'use strict';
66

77
const docsUrl = require('../util/docsUrl');
8+
const jsxUtil = require('../util/jsx');
89

910
// ------------------------------------------------------------------------------
1011
// Rule Definition
@@ -39,13 +40,8 @@ module.exports = {
3940
},
4041

4142
create: function(context) {
42-
const tagConvention = /^[a-z]|\-/;
43-
function isTagName(name) {
44-
return tagConvention.test(name);
45-
}
46-
4743
function isComponent(node) {
48-
return node.name && node.name.type === 'JSXIdentifier' && !isTagName(node.name.name);
44+
return node.name && node.name.type === 'JSXIdentifier' && !jsxUtil.isDOMComponent(node);
4945
}
5046

5147
function hasChildren(node) {
@@ -63,7 +59,7 @@ module.exports = {
6359
const configuration = Object.assign({}, optionDefaults, context.options[0]);
6460
return (
6561
configuration.component && isComponent(node) ||
66-
configuration.html && isTagName(node.name.name)
62+
configuration.html && jsxUtil.isDOMComponent(node)
6763
) && !node.selfClosing && !hasChildren(node);
6864
}
6965

lib/util/jsx.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/**
2+
* @fileoverview Utility functions for JSX
3+
*/
4+
'use strict';
5+
6+
const elementType = require('jsx-ast-utils/elementType');
7+
8+
const COMPAT_TAG_REGEX = /^[a-z]|\-/;
9+
10+
/**
11+
* Checks if a node represents a DOM element.
12+
* @param {String} node - JSXOpeningElement to check.
13+
* @returns {boolean} Whether or not the node corresponds to a DOM element.
14+
*/
15+
function isDOMComponent(node) {
16+
let name = elementType(node);
17+
18+
// Get namespace if the type is JSXNamespacedName or JSXMemberExpression
19+
if (name.indexOf(':') > -1) {
20+
name = name.slice(0, name.indexOf(':'));
21+
} else if (name.indexOf('.') > -1) {
22+
name = name.slice(0, name.indexOf('.'));
23+
}
24+
25+
return COMPAT_TAG_REGEX.test(name);
26+
}
27+
28+
module.exports = {
29+
isDOMComponent: isDOMComponent
30+
};

tests/lib/rules/jsx-no-bind.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,25 @@ ruleTester.run('jsx-no-bind', rule, {
268268
' }',
269269
'}'
270270
].join('\n')
271+
},
272+
273+
// ignore DOM components
274+
{
275+
code: '<div onClick={this._handleClick.bind(this)}></div>',
276+
options: [{ignoreDOMComponents: true}]
277+
},
278+
{
279+
code: '<div onClick={() => alert("1337")}></div>',
280+
options: [{ignoreDOMComponents: true}]
281+
},
282+
{
283+
code: '<div onClick={function () { alert("1337") }}></div>',
284+
options: [{ignoreDOMComponents: true}]
285+
},
286+
{
287+
code: '<div foo={::this.onChange} />',
288+
options: [{ignoreDOMComponents: true}],
289+
parser: 'babel-eslint'
271290
}
272291
],
273292

@@ -772,6 +791,13 @@ ruleTester.run('jsx-no-bind', rule, {
772791
].join('\n'),
773792
errors: [{message: 'JSX props should not use ::'}],
774793
parser: 'babel-eslint'
794+
},
795+
796+
// ignore DOM components
797+
{
798+
code: '<Foo onClick={this._handleClick.bind(this)} />',
799+
options: [{ignoreDOMComponents: true}],
800+
errors: [{message: 'JSX props should not use .bind()'}]
775801
}
776802
]
777803
});

0 commit comments

Comments
 (0)