Skip to content

Commit 0d7019b

Browse files
SeanHayesljharb
authored andcommitted
[New] no-adjacent-inline-elements: Prevent adjacent inline elements not separated by whitespace
1 parent c1ed90e commit 0d7019b

File tree

5 files changed

+243
-0
lines changed

5 files changed

+243
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2808,3 +2808,4 @@ If you're still not using React 15 you can keep the old behavior by setting the
28082808
[`jsx-curly-newline`]: docs/rules/jsx-curly-newline.md
28092809
[`jsx-no-useless-fragment`]: docs/rules/jsx-no-useless-fragment.md
28102810
[`jsx-no-script-url`]: docs/rules/jsx-no-script-url.md
2811+
[`no-adjacent-inline-elements`]: docs/rules/no-adjacent-inline-elements.md
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Prevent adjacent inline elements not separated by whitespace. (no-adjacent-inline-elements)
2+
3+
Adjacent inline elements not separated by whitespace will bump up against each
4+
other when viewed in an unstyled manner, which usually isn't desirable.
5+
6+
## Rule Details
7+
8+
The following patterns are considered warnings:
9+
10+
```jsx
11+
<div><a></a><a></a></div>
12+
<div><a></a><span></span></div>
13+
14+
React.createElement("div", undefined, [React.createElement("a"), React.createElement("span")]);
15+
```
16+
17+
The following patterns are not considered warnings:
18+
19+
```jsx
20+
<div><div></div><div></div></div>
21+
<div><a></a> <a></a></div>
22+
23+
React.createElement("div", undefined, [React.createElement("a"), " ", React.createElement("a")]);
24+
```

index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ const allRules = {
5252
'jsx-uses-vars': require('./lib/rules/jsx-uses-vars'),
5353
'jsx-wrap-multilines': require('./lib/rules/jsx-wrap-multilines'),
5454
'no-access-state-in-setstate': require('./lib/rules/no-access-state-in-setstate'),
55+
'no-adjacent-inline-elements': require('./lib/rules/no-adjacent-inline-elements'),
5556
'no-array-index-key': require('./lib/rules/no-array-index-key'),
5657
'no-children-prop': require('./lib/rules/no-children-prop'),
5758
'no-danger': require('./lib/rules/no-danger'),
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/**
2+
* @fileoverview Prevent adjacent inline elements not separated by whitespace.
3+
* @author Sean Hayes
4+
*/
5+
6+
'use strict';
7+
8+
// ------------------------------------------------------------------------------
9+
// Helpers
10+
// ------------------------------------------------------------------------------
11+
12+
// https://developer.mozilla.org/en-US/docs/Web/HTML/Inline_elements
13+
const inlineNames = [
14+
'a',
15+
'b',
16+
'big',
17+
'i',
18+
'small',
19+
'tt',
20+
'abbr',
21+
'acronym',
22+
'cite',
23+
'code',
24+
'dfn',
25+
'em',
26+
'kbd',
27+
'strong',
28+
'samp',
29+
'time',
30+
'var',
31+
'bdo',
32+
'br',
33+
'img',
34+
'map',
35+
'object',
36+
'q',
37+
'script',
38+
'span',
39+
'sub',
40+
'sup',
41+
'button',
42+
'input',
43+
'label',
44+
'select',
45+
'textarea'
46+
];
47+
// Note: raw &nbsp; will be transformed into \u00a0.
48+
const whitespaceRegex = /(?:^\s|\s$)/;
49+
50+
function isInline(node) {
51+
if (node.type === 'Literal') {
52+
// Regular whitespace will be removed.
53+
const value = node.value;
54+
// To properly separate inline elements, each end of the literal will need
55+
// whitespace.
56+
return !whitespaceRegex.test(value);
57+
}
58+
if (node.type === 'JSXElement' && inlineNames.indexOf(node.openingElement.name.name) > -1) {
59+
return true;
60+
}
61+
if (node.type === 'CallExpression' && inlineNames.indexOf(node.arguments[0].value) > -1) {
62+
return true;
63+
}
64+
return false;
65+
}
66+
67+
const ERROR = 'Child elements which render as inline HTML elements should be separated by a space or wrapped in block level elements.';
68+
69+
// ------------------------------------------------------------------------------
70+
// Rule Definition
71+
// ------------------------------------------------------------------------------
72+
73+
module.exports = {
74+
ERROR,
75+
meta: {
76+
docs: {
77+
description: 'Prevent adjacent inline elements not separated by whitespace.',
78+
category: 'Best Practices',
79+
recommended: false
80+
},
81+
schema: []
82+
},
83+
create(context) {
84+
function validate(node, children) {
85+
let currentIsInline = false;
86+
let previousIsInline = false;
87+
for (let i = 0; i < children.length; i++) {
88+
currentIsInline = isInline(children[i]);
89+
if (previousIsInline && currentIsInline) {
90+
context.report({
91+
node,
92+
message: ERROR
93+
});
94+
return;
95+
}
96+
previousIsInline = currentIsInline;
97+
}
98+
}
99+
return {
100+
JSXElement(node) {
101+
validate(node, node.children);
102+
},
103+
CallExpression(node) {
104+
if (!node.callee || node.callee.type !== 'MemberExpression' || node.callee.property.name !== 'createElement') {
105+
return;
106+
}
107+
if (node.arguments.length < 2) {
108+
return;
109+
}
110+
const children = node.arguments[2].elements;
111+
validate(node, children);
112+
}
113+
};
114+
}
115+
};
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/**
2+
* @fileoverview Tests for no-adjacent-inline-elements
3+
* @author Sean Hayes
4+
*/
5+
6+
'use strict';
7+
8+
// -----------------------------------------------------------------------------
9+
// Requirements
10+
// -----------------------------------------------------------------------------
11+
12+
const RuleTester = require('eslint').RuleTester;
13+
const rule = require('../../../lib/rules/no-adjacent-inline-elements');
14+
15+
const ERROR = rule.ERROR;
16+
17+
const parserOptions = {
18+
ecmaVersion: 6,
19+
ecmaFeatures: {
20+
experimentalObjectRestSpread: true,
21+
jsx: true
22+
}
23+
};
24+
25+
// -----------------------------------------------------------------------------
26+
// Tests
27+
// -----------------------------------------------------------------------------
28+
29+
const ruleTester = new RuleTester();
30+
ruleTester.run('no-adjacent-inline-elements', rule, {
31+
valid: [
32+
{
33+
code: '<div />;',
34+
parserOptions
35+
},
36+
{
37+
code: '<div><div></div><div></div></div>;',
38+
parserOptions
39+
},
40+
{
41+
code: '<div><p></p><div></div></div>;',
42+
parserOptions
43+
},
44+
{
45+
code: '<div><p></p><a></a></div>;',
46+
parserOptions
47+
},
48+
{
49+
code: '<div><a></a>&nbsp;<a></a></div>;',
50+
parserOptions
51+
},
52+
{
53+
code: '<div><a></a>&nbsp;some text &nbsp; <a></a></div>;',
54+
parserOptions
55+
},
56+
{
57+
code: '<div><a></a>&nbsp;some text <a></a></div>;',
58+
parserOptions
59+
},
60+
{
61+
code: '<div><a></a> <a></a></div>;',
62+
parserOptions
63+
},
64+
{
65+
code: '<div><ul><li><a></a></li><li><a></a></li></ul></div>;',
66+
parserOptions
67+
},
68+
{
69+
code: '<div><a></a> some text <a></a></div>;',
70+
errors: [{message: ERROR}],
71+
parserOptions
72+
},
73+
{
74+
code: ('React.createElement("div", undefined, [React.createElement("a"), ' +
75+
'" some text ", React.createElement("a")]);'),
76+
errors: [{message: ERROR}],
77+
parserOptions
78+
},
79+
{
80+
code: 'React.createElement("div", undefined, [React.createElement("a"), " ", React.createElement("a")]);',
81+
errors: [{message: ERROR}],
82+
parserOptions
83+
}
84+
],
85+
invalid: [
86+
{
87+
code: '<div><a></a><a></a></div>;',
88+
errors: [{message: ERROR}],
89+
parserOptions
90+
},
91+
{
92+
code: '<div><a></a><span></span></div>;',
93+
errors: [{message: ERROR}],
94+
parserOptions
95+
},
96+
{
97+
code: 'React.createElement("div", undefined, [React.createElement("a"), React.createElement("span")]);',
98+
errors: [{message: ERROR}],
99+
parserOptions
100+
}
101+
]
102+
});

0 commit comments

Comments
 (0)