Repository: jsx-eslint/eslint-plugin-react (js)

Issue:
New option: `jsx-one-expression-per-line` `non-jsx`
Examples of **incorrect** code for this rule:
```jsx
<Hello
  foo="bar
>Children
</Hello>
```
Examples of **correct** code for this rule:
```jsx
<Hello
  foo="bar
>
  Children
</Hello>
```

Pull request:
[New] `jsx-one-expression-per-line`: add `non-jsx` option to allow non-JSX children in one line
Fixes #3669

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0aa9588e..0b95e456 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
 * [`jsx-boolean-value`]: add `assumeUndefinedIsFalse` option ([#3675][] @developer-bandi)
 * `linkAttribute` setting, [`jsx-no-target-blank`]: support multiple properties ([#3673][] @burtek)
 * [`jsx-no-script-url`]: add `includeFromSettings` option to support `linkAttributes` setting ([#3673][] @burtek)
+* [`jsx-one-expression-per-line`]: add `non-jsx` option to allow non-JSX children in one line ([#3677][] @burtek)
 
 ### Fixed
 * [`jsx-no-leaked-render`]: preserve RHS parens for multiline jsx elements while fixing ([#3623][] @akulsr0)
@@ -32,6 +33,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
 * [Docs] [`jsx-key`]: fix correct example ([#3656][] @developer-bandi)
 * [Tests] `jsx-wrap-multilines`: passing tests ([#3545][] @burtek)
 
+[#3677]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3677
 [#3675]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3675
 [#3674]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3674
 [#3673]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3673
diff --git a/docs/rules/jsx-one-expression-per-line.md b/docs/rules/jsx-one-expression-per-line.md
index 9c5fe0ea..d2f17027 100644
--- a/docs/rules/jsx-one-expression-per-line.md
+++ b/docs/rules/jsx-one-expression-per-line.md
@@ -133,3 +133,11 @@ Examples of **correct** code for this rule, when configured as `"single-child"`:
 
 <App><Hello /></App>
 ```
+
+Examples of **correct** code for this rule, when configured as `"non-jsx"`:
+
+```jsx
+<App>Hello {someVariable}</App>
+
+<App>Hello {<Hello />} there!</App>
+```
diff --git a/lib/rules/jsx-one-expression-per-line.js b/lib/rules/jsx-one-expression-per-line.js
index 6d0d1575..4c5a9c97 100644
--- a/lib/rules/jsx-one-expression-per-line.js
+++ b/lib/rules/jsx-one-expression-per-line.js
@@ -38,7 +38,7 @@ module.exports = {
         type: 'object',
         properties: {
           allow: {
-            enum: ['none', 'literal', 'single-child'],
+            enum: ['none', 'literal', 'single-child', 'non-jsx'],
           },
         },
         default: optionDefaults,
@@ -65,6 +65,13 @@ module.exports = {
         return;
       }
 
+      if (
+        options.allow === 'non-jsx'
+        && !children.find((child) => (child.type === 'JSXFragment' || child.type === 'JSXElement'))
+      ) {
+        return;
+      }
+
       const openingElement = node.openingElement || node.openingFragment;
       const closingElement = node.closingElement || node.closingFragment;
       const openingElementStartLine = openingElement.loc.start.line;


Test patch:
diff --git a/tests/lib/rules/jsx-one-expression-per-line.js b/tests/lib/rules/jsx-one-expression-per-line.js
index 0c684c7d..62afc430 100644
--- a/tests/lib/rules/jsx-one-expression-per-line.js
+++ b/tests/lib/rules/jsx-one-expression-per-line.js
@@ -155,6 +155,22 @@ ruleTester.run('jsx-one-expression-per-line', rule, {
       code: '<App>{"foo"}</App>',
       options: [{ allow: 'single-child' }],
     },
+    {
+      code: '<App>123</App>',
+      options: [{ allow: 'non-jsx' }],
+    },
+    {
+      code: '<App>foo</App>',
+      options: [{ allow: 'non-jsx' }],
+    },
+    {
+      code: '<App>{"foo"}</App>',
+      options: [{ allow: 'non-jsx' }],
+    },
+    {
+      code: '<App>{<Bar />}</App>',
+      options: [{ allow: 'non-jsx' }],
+    },
     {
       code: '<App>{foo && <Bar />}</App>',
       options: [{ allow: 'single-child' }],
@@ -184,6 +200,38 @@ ruleTester.run('jsx-one-expression-per-line', rule, {
       `,
       features: ['fragment', 'no-ts-old'], // TODO: FIXME: remove no-ts-old and fix
     },
+    {
+      code: '<App>Hello {name}</App>',
+      options: [{ allow: 'non-jsx' }],
+    },
+    {
+      code: `
+        <App>
+          Hello {name} there!
+        </App>`,
+      options: [{ allow: 'non-jsx' }],
+    },
+    {
+      code: `
+        <App>
+          Hello {<Bar />} there!
+        </App>`,
+      options: [{ allow: 'non-jsx' }],
+    },
+    {
+      code: `
+        <App>
+          Hello {(<Bar />)} there!
+        </App>`,
+      options: [{ allow: 'non-jsx' }],
+    },
+    {
+      code: `
+        <App>
+          Hello {(() => <Bar />)()} there!
+        </App>`,
+      options: [{ allow: 'non-jsx' }],
+    },
   ]),
 
   invalid: parsers.all([
@@ -493,6 +541,28 @@ foo
       ],
       parserOptions,
     },
+    {
+      code: `
+        <Text style={styles.foo}>
+          <Bar /> <Baz />
+        </Text>
+      `,
+      output: `
+        <Text style={styles.foo}>
+          <Bar />${' '/* intentional trailing space */}
+{' '}
+<Baz />
+        </Text>
+      `,
+      errors: [
+        {
+          messageId: 'moveToNewLine',
+          data: { descriptor: 'Baz' },
+        },
+      ],
+      options: [{ allow: 'non-jsx' }],
+      parserOptions,
+    },
     {
       code: `
         <Text style={styles.foo}>
@@ -1257,6 +1327,23 @@ foo
         },
       ],
     },
+    {
+      code: `
+        <App><Foo /></App>
+      `,
+      output: `
+        <App>
+<Foo />
+</App>
+      `,
+      options: [{ allow: 'non-jsx' }],
+      errors: [
+        {
+          messageId: 'moveToNewLine',
+          data: { descriptor: 'Foo' },
+        },
+      ],
+    },
     {
       code: `
         <App