Skip to content

Commit

Permalink
Prevent adjacent inline elements not separated by whitespace.
Browse files Browse the repository at this point in the history
  • Loading branch information
SeanHayes committed Apr 22, 2017
1 parent fd90dae commit 6479675
Show file tree
Hide file tree
Showing 4 changed files with 246 additions and 1 deletion.
24 changes: 24 additions & 0 deletions docs/rules/no-adjacent-inline-elements.md
@@ -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")]);
```
3 changes: 2 additions & 1 deletion index.js
Expand Up @@ -64,7 +64,8 @@ var allRules = {
'no-comment-textnodes': require('./lib/rules/no-comment-textnodes'),
'require-extension': require('./lib/rules/require-extension'),
'wrap-multilines': require('./lib/rules/wrap-multilines'),
'jsx-tag-spacing': require('./lib/rules/jsx-tag-spacing')
'jsx-tag-spacing': require('./lib/rules/jsx-tag-spacing'),
'no-adjacent-inline-elements': require('./lib/rules/no-adjacent-inline-elements')
};

function filterRules(rules, predicate) {
Expand Down
118 changes: 118 additions & 0 deletions lib/rules/no-adjacent-inline-elements.js
@@ -0,0 +1,118 @@
/**
* @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
var 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.
var whitespaceStart = /^\s/;
var whitespaceEnd = /\s$/;

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

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

'use strict';

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

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

var ERROR = rule.ERROR;

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

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

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

0 comments on commit 6479675

Please sign in to comment.