Skip to content

Commit

Permalink
[New] no-adjacent-inline-elements: Prevent adjacent inline elements…
Browse files Browse the repository at this point in the history
… not separated by whitespace
  • Loading branch information
SeanHayes authored and ljharb committed Apr 19, 2017
1 parent c1ed90e commit 0d7019b
Show file tree
Hide file tree
Showing 5 changed files with 243 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2808,3 +2808,4 @@ If you're still not using React 15 you can keep the old behavior by setting the
[`jsx-curly-newline`]: docs/rules/jsx-curly-newline.md
[`jsx-no-useless-fragment`]: docs/rules/jsx-no-useless-fragment.md
[`jsx-no-script-url`]: docs/rules/jsx-no-script-url.md
[`no-adjacent-inline-elements`]: docs/rules/no-adjacent-inline-elements.md
24 changes: 24 additions & 0 deletions docs/rules/no-adjacent-inline-elements.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Prevent adjacent inline elements not separated by whitespace. (no-adjacent-inline-elements)

Adjacent inline elements not separated by whitespace will bump up against each
other when viewed in an unstyled manner, which usually isn't desirable.

## Rule Details

The following patterns are considered warnings:

```jsx
<div><a></a><a></a></div>
<div><a></a><span></span></div>

React.createElement("div", undefined, [React.createElement("a"), React.createElement("span")]);
```

The following patterns are not considered warnings:

```jsx
<div><div></div><div></div></div>
<div><a></a> <a></a></div>

React.createElement("div", undefined, [React.createElement("a"), " ", React.createElement("a")]);
```
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ const allRules = {
'jsx-uses-vars': require('./lib/rules/jsx-uses-vars'),
'jsx-wrap-multilines': require('./lib/rules/jsx-wrap-multilines'),
'no-access-state-in-setstate': require('./lib/rules/no-access-state-in-setstate'),
'no-adjacent-inline-elements': require('./lib/rules/no-adjacent-inline-elements'),
'no-array-index-key': require('./lib/rules/no-array-index-key'),
'no-children-prop': require('./lib/rules/no-children-prop'),
'no-danger': require('./lib/rules/no-danger'),
Expand Down
115 changes: 115 additions & 0 deletions lib/rules/no-adjacent-inline-elements.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/**
* @fileoverview Prevent adjacent inline elements not separated by whitespace.
* @author Sean Hayes
*/

'use strict';

// ------------------------------------------------------------------------------
// Helpers
// ------------------------------------------------------------------------------

// https://developer.mozilla.org/en-US/docs/Web/HTML/Inline_elements
const inlineNames = [
'a',
'b',
'big',
'i',
'small',
'tt',
'abbr',
'acronym',
'cite',
'code',
'dfn',
'em',
'kbd',
'strong',
'samp',
'time',
'var',
'bdo',
'br',
'img',
'map',
'object',
'q',
'script',
'span',
'sub',
'sup',
'button',
'input',
'label',
'select',
'textarea'
];
// Note: raw &nbsp; will be transformed into \u00a0.
const whitespaceRegex = /(?:^\s|\s$)/;

function isInline(node) {
if (node.type === 'Literal') {
// Regular whitespace will be removed.
const value = node.value;
// To properly separate inline elements, each end of the literal will need
// whitespace.
return !whitespaceRegex.test(value);
}
if (node.type === 'JSXElement' && inlineNames.indexOf(node.openingElement.name.name) > -1) {
return true;
}
if (node.type === 'CallExpression' && inlineNames.indexOf(node.arguments[0].value) > -1) {
return true;
}
return false;
}

const ERROR = 'Child elements which render as inline HTML elements should be separated by a space or wrapped in block level elements.';

// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------

module.exports = {
ERROR,
meta: {
docs: {
description: 'Prevent adjacent inline elements not separated by whitespace.',
category: 'Best Practices',
recommended: false
},
schema: []
},
create(context) {
function validate(node, children) {
let currentIsInline = false;
let previousIsInline = false;
for (let i = 0; i < children.length; i++) {
currentIsInline = isInline(children[i]);
if (previousIsInline && currentIsInline) {
context.report({
node,
message: ERROR
});
return;
}
previousIsInline = currentIsInline;
}
}
return {
JSXElement(node) {
validate(node, node.children);
},
CallExpression(node) {
if (!node.callee || node.callee.type !== 'MemberExpression' || node.callee.property.name !== 'createElement') {
return;
}
if (node.arguments.length < 2) {
return;
}
const children = node.arguments[2].elements;
validate(node, children);
}
};
}
};
102 changes: 102 additions & 0 deletions tests/lib/rules/no-adjacent-inline-elements.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/**
* @fileoverview Tests for no-adjacent-inline-elements
* @author Sean Hayes
*/

'use strict';

// -----------------------------------------------------------------------------
// Requirements
// -----------------------------------------------------------------------------

const RuleTester = require('eslint').RuleTester;
const rule = require('../../../lib/rules/no-adjacent-inline-elements');

const ERROR = rule.ERROR;

const parserOptions = {
ecmaVersion: 6,
ecmaFeatures: {
experimentalObjectRestSpread: true,
jsx: true
}
};

// -----------------------------------------------------------------------------
// Tests
// -----------------------------------------------------------------------------

const ruleTester = new RuleTester();
ruleTester.run('no-adjacent-inline-elements', rule, {
valid: [
{
code: '<div />;',
parserOptions
},
{
code: '<div><div></div><div></div></div>;',
parserOptions
},
{
code: '<div><p></p><div></div></div>;',
parserOptions
},
{
code: '<div><p></p><a></a></div>;',
parserOptions
},
{
code: '<div><a></a>&nbsp;<a></a></div>;',
parserOptions
},
{
code: '<div><a></a>&nbsp;some text &nbsp; <a></a></div>;',
parserOptions
},
{
code: '<div><a></a>&nbsp;some text <a></a></div>;',
parserOptions
},
{
code: '<div><a></a> <a></a></div>;',
parserOptions
},
{
code: '<div><ul><li><a></a></li><li><a></a></li></ul></div>;',
parserOptions
},
{
code: '<div><a></a> some text <a></a></div>;',
errors: [{message: ERROR}],
parserOptions
},
{
code: ('React.createElement("div", undefined, [React.createElement("a"), ' +
'" some text ", React.createElement("a")]);'),
errors: [{message: ERROR}],
parserOptions
},
{
code: 'React.createElement("div", undefined, [React.createElement("a"), " ", React.createElement("a")]);',
errors: [{message: ERROR}],
parserOptions
}
],
invalid: [
{
code: '<div><a></a><a></a></div>;',
errors: [{message: ERROR}],
parserOptions
},
{
code: '<div><a></a><span></span></div>;',
errors: [{message: ERROR}],
parserOptions
},
{
code: 'React.createElement("div", undefined, [React.createElement("a"), React.createElement("span")]);',
errors: [{message: ERROR}],
parserOptions
}
]
});

0 comments on commit 0d7019b

Please sign in to comment.