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 +
+ + +
+``` + +```jsx +
+ + {showSomething === true && } +
+``` + +```jsx +
+ {showSomething === true && } + {showSomethingElse === true ? ( + + ) : ( + + )} +
+``` + +The following patterns are **not** considered warnings: + +```jsx +
+ + + + + + + {showSomething === true && } + + + + {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: [ + ` +
+ + + + + + + {showSomething === true && } + + + + {showSomethingElse === true ? ( + + ) : ( + + )} +
+ `, + { + code: ` + <> + + Test + + Should be in new line + + `, + parser: parsers.BABEL_ESLINT + } + ], + invalid: [ + { + code: ` +
+ + +
+ `, + output: ` +
+ + + +
+ `, + errors: [{ + message: 'JSX element should start in a new line' + }] + }, + { + code: ` +
+ + {showSomething === true && } +
+ `, + output: ` +
+ + + {showSomething === true && } +
+ `, + errors: [{ + message: 'JSX element should start in a new line' + }] + }, + { + code: ` +
+ {showSomething === true && } + +
+ `, + output: ` +
+ {showSomething === true && } + + +
+ `, + 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: ` + <> + + Test + Should be in new line + + `, + output: ` + <> + + Test + + Should be in new line + + `, + errors: [ + {message: 'JSX element should start in a new line'} + ], + parser: parsers.BABEL_ESLINT + } + ] +});