From 0d7019b48f1d42bb34065c5a321ec66acbc264ca Mon Sep 17 00:00:00 2001 From: Sean Hayes Date: Wed, 19 Apr 2017 13:13:13 -0700 Subject: [PATCH] [New] `no-adjacent-inline-elements`: Prevent adjacent inline elements not separated by whitespace --- CHANGELOG.md | 1 + docs/rules/no-adjacent-inline-elements.md | 24 ++++ index.js | 1 + lib/rules/no-adjacent-inline-elements.js | 115 ++++++++++++++++++ .../lib/rules/no-adjacent-inline-elements.js | 102 ++++++++++++++++ 5 files changed, 243 insertions(+) create mode 100644 docs/rules/no-adjacent-inline-elements.md create mode 100644 lib/rules/no-adjacent-inline-elements.js create mode 100644 tests/lib/rules/no-adjacent-inline-elements.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 99a23280e5..b67ee92808 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/docs/rules/no-adjacent-inline-elements.md b/docs/rules/no-adjacent-inline-elements.md new file mode 100644 index 0000000000..db7d9b41ec --- /dev/null +++ b/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 +
+
+ +React.createElement("div", undefined, [React.createElement("a"), React.createElement("span")]); +``` + +The following patterns are not considered warnings: + +```jsx +
+
+ +React.createElement("div", undefined, [React.createElement("a"), " ", React.createElement("a")]); +``` diff --git a/index.js b/index.js index 6db76d0aee..2396353682 100644 --- a/index.js +++ b/index.js @@ -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'), diff --git a/lib/rules/no-adjacent-inline-elements.js b/lib/rules/no-adjacent-inline-elements.js new file mode 100644 index 0000000000..60104542b4 --- /dev/null +++ b/lib/rules/no-adjacent-inline-elements.js @@ -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   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); + } + }; + } +}; diff --git a/tests/lib/rules/no-adjacent-inline-elements.js b/tests/lib/rules/no-adjacent-inline-elements.js new file mode 100644 index 0000000000..c196a0ee20 --- /dev/null +++ b/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 +// ----------------------------------------------------------------------------- + +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: '
;', + parserOptions + }, + { + code: '
;', + parserOptions + }, + { + code: '

;', + parserOptions + }, + { + code: '

;', + parserOptions + }, + { + code: '
 
;', + parserOptions + }, + { + code: '
 some text  
;', + parserOptions + }, + { + code: '
 some text
;', + parserOptions + }, + { + code: '
;', + parserOptions + }, + { + code: '
;', + parserOptions + }, + { + code: '
some text
;', + 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: '
;', + errors: [{message: ERROR}], + parserOptions + }, + { + code: '
;', + errors: [{message: ERROR}], + parserOptions + }, + { + code: 'React.createElement("div", undefined, [React.createElement("a"), React.createElement("span")]);', + errors: [{message: ERROR}], + parserOptions + } + ] +});