diff --git a/index.js b/index.js index 6c1a933ac9..765f681cd0 100644 --- a/index.js +++ b/index.js @@ -9,9 +9,10 @@ const allRules = { 'display-name': require('./lib/rules/display-name'), 'forbid-component-props': require('./lib/rules/forbid-component-props'), 'forbid-elements': require('./lib/rules/forbid-elements'), - 'forbid-prop-types': require('./lib/rules/forbid-prop-types'), 'forbid-foreign-prop-types': require('./lib/rules/forbid-foreign-prop-types'), + 'forbid-prop-types': require('./lib/rules/forbid-prop-types'), 'jsx-boolean-value': require('./lib/rules/jsx-boolean-value'), + 'jsx-child-element-spacing': require('./lib/rules/jsx-child-element-spacing'), 'jsx-closing-bracket-location': require('./lib/rules/jsx-closing-bracket-location'), 'jsx-closing-tag-location': require('./lib/rules/jsx-closing-tag-location'), 'jsx-curly-spacing': require('./lib/rules/jsx-curly-spacing'), @@ -23,12 +24,12 @@ const allRules = { 'jsx-indent-props': require('./lib/rules/jsx-indent-props'), 'jsx-key': require('./lib/rules/jsx-key'), 'jsx-max-props-per-line': require('./lib/rules/jsx-max-props-per-line'), - 'jsx-one-expression-per-line': require('./lib/rules/jsx-one-expression-per-line'), 'jsx-no-bind': require('./lib/rules/jsx-no-bind'), 'jsx-no-comment-textnodes': require('./lib/rules/jsx-no-comment-textnodes'), 'jsx-no-duplicate-props': require('./lib/rules/jsx-no-duplicate-props'), 'jsx-no-literals': require('./lib/rules/jsx-no-literals'), 'jsx-no-target-blank': require('./lib/rules/jsx-no-target-blank'), + 'jsx-one-expression-per-line': require('./lib/rules/jsx-one-expression-per-line'), 'button-has-type': require('./lib/rules/button-has-type'), 'jsx-no-undef': require('./lib/rules/jsx-no-undef'), 'jsx-curly-brace-presence': require('./lib/rules/jsx-curly-brace-presence'), diff --git a/lib/rules/jsx-child-element-spacing.js b/lib/rules/jsx-child-element-spacing.js new file mode 100644 index 0000000000..82d56917cb --- /dev/null +++ b/lib/rules/jsx-child-element-spacing.js @@ -0,0 +1,103 @@ +'use strict'; + +// This list is taken from https://developer.mozilla.org/en-US/docs/Web/HTML/Inline_elements +const INLINE_ELEMENTS = new Set([ + 'a', + 'abbr', + 'acronym', + 'b', + 'bdo', + 'big', + 'br', + 'button', + 'cite', + 'code', + 'dfn', + 'em', + 'i', + 'img', + 'input', + 'kbd', + 'label', + 'map', + 'object', + 'q', + 'samp', + 'script', + 'select', + 'small', + 'span', + 'strong', + 'sub', + 'sup', + 'textarea', + 'tt', + 'var' +]); + +module.exports = { + meta: { + docs: { + description: 'Ensures inline tags are not rendered without spaces between them', + category: 'Stylistic Issues', + recommended: false + }, + fixable: false, + schema: [ + { + type: 'object', + properties: {}, + default: {}, + additionalProperties: false + } + ] + }, + create: function (context) { + const elementName = node => ( + node.openingElement && + node.openingElement.name && + node.openingElement.name.type === 'JSXIdentifier' && + node.openingElement.name.name + ); + + const isInlineElement = node => ( + node.type === 'JSXElement' && + INLINE_ELEMENTS.has(elementName(node)) + ); + + const TEXT_FOLLOWING_ELEMENT_PATTERN = /^\s*\n\s*\S/; + const TEXT_PRECEDING_ELEMENT_PATTERN = /\S\s*\n\s*$/; + + return { + JSXElement: function(node) { + let lastChild = null; + let child = null; + (node.children.concat([null])).forEach(nextChild => { + if ( + (lastChild || nextChild) && + (!lastChild || isInlineElement(lastChild)) && + (child && child.type === 'Literal') && + (!nextChild || isInlineElement(nextChild)) && + true + ) { + if (lastChild && child.value.match(TEXT_FOLLOWING_ELEMENT_PATTERN)) { + context.report({ + node: child, + loc: child.loc, + message: `Ambiguous spacing after previous element ${elementName(lastChild)}` + }); + } else if (nextChild && child.value.match(TEXT_PRECEDING_ELEMENT_PATTERN)) { + context.report({ + node: child, + loc: child.loc, + message: `Ambiguous spacing before next element ${elementName(nextChild)}` + }); + } + } + lastChild = child; + child = nextChild; + }); + } + }; + } +}; diff --git a/tests/lib/rules/jsx-child-element-spacing.js b/tests/lib/rules/jsx-child-element-spacing.js new file mode 100644 index 0000000000..51ebfcd671 --- /dev/null +++ b/tests/lib/rules/jsx-child-element-spacing.js @@ -0,0 +1,196 @@ +'use strict'; + +const rule = require('../../../lib/rules/jsx-child-element-spacing'); +const RuleTester = require('eslint').RuleTester; +const parserOptions = { + sourceType: 'module', + ecmaFeatures: { + jsx: true + } +}; + +const ruleTester = new RuleTester({parserOptions}); +ruleTester.run('jsx-child-element-spacing', rule, { + valid: [{ + code: ` + + foo + + ` + }, { + code: ` + + bar + + ` + }, { + code: ` + + + nested + + + ` + }, { + code: ` + + foo + bar + + ` + }, { + code: ` + + foobarbaz + + ` + }, { + code: ` + + foo + {' '} + bar + {' '} + baz + + ` + }, { + code: ` + + foo + {' '}bar{' '} + baz + + ` + }, { + code: ` + + foo{' '} + bar + {' '}baz + + ` + }, { + code: ` + + foo{/* + */}bar{/* + */}baz + + ` + }, { + code: ` + + Please take a look at this link. + + ` + }, { + code: ` + + Please take a look at + {' '} + this link. + + ` + }, { + code: ` + +

A

+

B

+
+ ` + }, { + code: ` + +

A

B

+
+ ` + }, { + code: ` + + foo + bar + + ` + }, { + code: ` + + + nested1 + nested2 + + + ` + }, { + code: ` + + A + B + + ` + }], + + invalid: [{ + code: ` + + foo + bar + + `, + errors: [ + {message: 'Ambiguous spacing before next element a'} + ] + }, { + code: ` + + bar + baz + + `, + errors: [ + {message: 'Ambiguous spacing after previous element a'} + ] + }, { + code: ` + + {' '}bar + baz + + `, + errors: [ + {message: 'Ambiguous spacing after previous element a'} + ] + }, { + code: ` + + Please take a look at + this link. + + `, + errors: [ + {message: 'Ambiguous spacing before next element a'} + ] + }, { + code: ` + + Some loops and some + if statements. + + `, + errors: [ + {message: 'Ambiguous spacing before next element code'} + ] + }, { + code: ` + + Here is + a link and here is + another + + `, + errors: [ + {message: 'Ambiguous spacing before next element a'}, + {message: 'Ambiguous spacing before next element a'} + ] + }] +});