diff --git a/docs/rules/jsx-newline.md b/docs/rules/jsx-newline.md
new file mode 100644
index 0000000000..39491ee419
--- /dev/null
+++ b/docs/rules/jsx-newline.md
@@ -0,0 +1,63 @@
+# Enforce a new line after jsx elements and expressions (react/jsx-newline)
+
+## Rule Details
+
+This is a stylistic rule intended to make JSX code more readable by enforcing spaces between adjacent JSX elements and expressions.
+
+The following patterns are considered warnings:
+
+```jsx
+
+ {data.label}
+
+
+```
+
+```jsx
+
+ {data.label}
+ {showSomething === true && }
+
+```
+
+```jsx
+
+ {showSomething === true && }
+ {showSomethingElse === true ? (
+
+ ) : (
+
+ )}
+
+```
+
+The following patterns are **not** considered warnings:
+
+```jsx
+
+ {data.label}
+
+
+
+
+
+ Button 2
+
+
+
+
+ {showSomething === true && }
+
+ Button 3
+
+ {showSomethingElse === true ? (
+
+ ) : (
+
+ )}
+
+```
+
+## When Not To Use It
+
+You can turn this rule off if you are not concerned with spacing between your JSX elements and expressions.
\ No newline at end of file
diff --git a/index.js b/index.js
index bc0c30a43d..7f2b7e7d9e 100644
--- a/index.js
+++ b/index.js
@@ -91,7 +91,8 @@ const allRules = {
'state-in-constructor': require('./lib/rules/state-in-constructor'),
'static-property-placement': require('./lib/rules/static-property-placement'),
'style-prop-object': require('./lib/rules/style-prop-object'),
- 'void-dom-elements-no-children': require('./lib/rules/void-dom-elements-no-children')
+ 'void-dom-elements-no-children': require('./lib/rules/void-dom-elements-no-children'),
+ 'jsx-newline': require('./lib/rules/jsx-newline')
};
/* eslint-enable */
diff --git a/lib/rules/jsx-newline.js b/lib/rules/jsx-newline.js
new file mode 100644
index 0000000000..0a773635e6
--- /dev/null
+++ b/lib/rules/jsx-newline.js
@@ -0,0 +1,61 @@
+/**
+ * @fileoverview Enforce a new line after jsx elements and expressions.
+ * @author Johnny Zabala
+ */
+
+'use strict';
+
+const arrayIncludes = require('array-includes');
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+module.exports = {
+ meta: {
+ docs: {
+ description: 'Enforce a new line after jsx elements and expressions',
+ category: 'Stylistic Issues',
+ recommended: false
+ },
+ fixable: 'code'
+ },
+ create(context) {
+ const jsxElementParents = new Set();
+ const sourceCode = context.getSourceCode();
+ return {
+ 'Program:exit'() {
+ jsxElementParents.forEach((parent) => {
+ parent.children.forEach((element, index, elements) => {
+ if (element.type === 'JSXElement' || element.type === 'JSXExpressionContainer') {
+ const firstAdjacentSibling = elements[index + 1];
+ const secondAdjacentSibling = elements[index + 2];
+ if (
+ firstAdjacentSibling
+ && secondAdjacentSibling
+ && arrayIncludes(['Literal', 'JSXText'], firstAdjacentSibling.type)
+ // Check adjacent sibling has the proper amount of newlines
+ && !/\n\s*\n/.test(firstAdjacentSibling.value)
+ ) {
+ context.report({
+ node: secondAdjacentSibling,
+ message: 'JSX element should start in a new line',
+ fix(fixer) {
+ return fixer.replaceText(
+ firstAdjacentSibling,
+ sourceCode.getText(firstAdjacentSibling)
+ .replace(/(\n)(?!.*\1)/g, '\n\n')
+ );
+ }
+ });
+ }
+ }
+ });
+ });
+ },
+ ':matches(JSXElement, JSXFragment) > :matches(JSXElement, JSXExpressionContainer)': (node) => {
+ jsxElementParents.add(node.parent);
+ }
+ };
+ }
+};
diff --git a/tests/lib/rules/jsx-newline.js b/tests/lib/rules/jsx-newline.js
new file mode 100644
index 0000000000..6eba504fb0
--- /dev/null
+++ b/tests/lib/rules/jsx-newline.js
@@ -0,0 +1,205 @@
+/**
+ * @fileoverview Enforce a new line after jsx elements and expressions
+ * @author Johnny Zabala
+ */
+
+'use strict';
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const RuleTester = require('eslint').RuleTester;
+const rule = require('../../../lib/rules/jsx-newline');
+
+const parsers = require('../../helpers/parsers');
+
+const parserOptions = {
+ ecmaVersion: 2018,
+ sourceType: 'module',
+ ecmaFeatures: {
+ jsx: true
+ }
+};
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+const ruleTester = new RuleTester({parserOptions});
+ruleTester.run('jsx-newline', rule, {
+ valid: [
+ `
+
+ {data.label}
+
+
+
+
+
+ Button 2
+
+
+
+
+ {showSomething === true && }
+
+ Button 3
+
+ {showSomethingElse === true ? (
+
+ ) : (
+
+ )}
+
+ `,
+ {
+ code: `
+ <>
+ {data.label}
+ Test
+
+ Should be in new line
+ >
+ `,
+ parser: parsers.BABEL_ESLINT
+ }
+ ],
+ invalid: [
+ {
+ code: `
+
+ {data.label}
+
+
+ `,
+ output: `
+
+ {data.label}
+
+
+
+ `,
+ errors: [{
+ message: 'JSX element should start in a new line'
+ }]
+ },
+ {
+ code: `
+
+ {data.label}
+ {showSomething === true && }
+
+ `,
+ output: `
+
+ {data.label}
+
+ {showSomething === true && }
+
+ `,
+ errors: [{
+ message: 'JSX element should start in a new line'
+ }]
+ },
+ {
+ code: `
+
+ {showSomething === true && }
+ {data.label}
+
+ `,
+ output: `
+
+ {showSomething === true && }
+
+ {data.label}
+
+ `,
+ errors: [{
+ message: 'JSX element should start in a new line'
+ }]
+ },
+ {
+ code: `
+
+ {showSomething === true && }
+ {showSomethingElse === true ? (
+
+ ) : (
+
+ )}
+
+ `,
+ output: `
+
+ {showSomething === true && }
+
+ {showSomethingElse === true ? (
+
+ ) : (
+
+ )}
+
+ `,
+ errors: [{
+ message: 'JSX element should start in a new line'
+ }]
+ },
+ {
+ code: `
+
+ `,
+ output: `
+
+ `,
+ errors: [
+ {message: 'JSX element should start in a new line'},
+ {message: 'JSX element should start in a new line'},
+ {message: 'JSX element should start in a new line'}
+ ]
+ },
+ {
+ code: `
+ <>
+ {data.label}
+ Test
+ Should be in new line
+ >
+ `,
+ output: `
+ <>
+ {data.label}
+ Test
+
+ Should be in new line
+ >
+ `,
+ errors: [
+ {message: 'JSX element should start in a new line'}
+ ],
+ parser: parsers.BABEL_ESLINT
+ }
+ ]
+});