From e82018cb485c1acc133537a2df702f5704f83f8c Mon Sep 17 00:00:00 2001 From: Sean Hayes Date: Wed, 19 Apr 2017 13:13:13 -0700 Subject: [PATCH] Prevent adjacent inline elements not separated by whitespace. --- index.js | 3 +- lib/rules/no-adjacent-inline-elements.js | 118 ++++++++++++++++++ .../lib/rules/no-adjacent-inline-elements.js | 102 +++++++++++++++ 3 files changed, 222 insertions(+), 1 deletion(-) create mode 100644 lib/rules/no-adjacent-inline-elements.js create mode 100644 tests/lib/rules/no-adjacent-inline-elements.js diff --git a/index.js b/index.js index 9fd15e15bf..3074bbc5fa 100644 --- a/index.js +++ b/index.js @@ -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) { diff --git a/lib/rules/no-adjacent-inline-elements.js b/lib/rules/no-adjacent-inline-elements.js new file mode 100644 index 0000000000..27afeb73b4 --- /dev/null +++ b/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   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); + } + }; + } +}; 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..07f4d0abab --- /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 +// ----------------------------------------------------------------------------- + +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: '
;', + parserOptions: parserOptions + }, + { + code: '
;', + parserOptions: parserOptions + }, + { + code: '

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

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