Skip to content

Commit 9f4b2b9

Browse files
burtekljharb
authored andcommitted
[New] jsx-one-expression-per-line: add non-jsx option to allow non-JSX children in one line
1 parent 1014f8c commit 9f4b2b9

File tree

4 files changed

+105
-1
lines changed

4 files changed

+105
-1
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
1717
* [`jsx-boolean-value`]: add `assumeUndefinedIsFalse` option ([#3675][] @developer-bandi)
1818
* `linkAttribute` setting, [`jsx-no-target-blank`]: support multiple properties ([#3673][] @burtek)
1919
* [`jsx-no-script-url`]: add `includeFromSettings` option to support `linkAttributes` setting ([#3673][] @burtek)
20+
* [`jsx-one-expression-per-line`]: add `non-jsx` option to allow non-JSX children in one line ([#3677][] @burtek)
2021

2122
### Fixed
2223
* [`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
3233
* [Docs] [`jsx-key`]: fix correct example ([#3656][] @developer-bandi)
3334
* [Tests] `jsx-wrap-multilines`: passing tests ([#3545][] @burtek)
3435

36+
[#3677]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3677
3537
[#3675]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3675
3638
[#3674]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3674
3739
[#3673]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3673

docs/rules/jsx-one-expression-per-line.md

+8
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,11 @@ Examples of **correct** code for this rule, when configured as `"single-child"`:
133133

134134
<App><Hello /></App>
135135
```
136+
137+
Examples of **correct** code for this rule, when configured as `"non-jsx"`:
138+
139+
```jsx
140+
<App>Hello {someVariable}</App>
141+
142+
<App>Hello {<Hello />} there!</App>
143+
```

lib/rules/jsx-one-expression-per-line.js

+8-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ module.exports = {
3838
type: 'object',
3939
properties: {
4040
allow: {
41-
enum: ['none', 'literal', 'single-child'],
41+
enum: ['none', 'literal', 'single-child', 'non-jsx'],
4242
},
4343
},
4444
default: optionDefaults,
@@ -65,6 +65,13 @@ module.exports = {
6565
return;
6666
}
6767

68+
if (
69+
options.allow === 'non-jsx'
70+
&& !children.find((child) => (child.type === 'JSXFragment' || child.type === 'JSXElement'))
71+
) {
72+
return;
73+
}
74+
6875
const openingElement = node.openingElement || node.openingFragment;
6976
const closingElement = node.closingElement || node.closingFragment;
7077
const openingElementStartLine = openingElement.loc.start.line;

tests/lib/rules/jsx-one-expression-per-line.js

+87
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,22 @@ ruleTester.run('jsx-one-expression-per-line', rule, {
155155
code: '<App>{"foo"}</App>',
156156
options: [{ allow: 'single-child' }],
157157
},
158+
{
159+
code: '<App>123</App>',
160+
options: [{ allow: 'non-jsx' }],
161+
},
162+
{
163+
code: '<App>foo</App>',
164+
options: [{ allow: 'non-jsx' }],
165+
},
166+
{
167+
code: '<App>{"foo"}</App>',
168+
options: [{ allow: 'non-jsx' }],
169+
},
170+
{
171+
code: '<App>{<Bar />}</App>',
172+
options: [{ allow: 'non-jsx' }],
173+
},
158174
{
159175
code: '<App>{foo && <Bar />}</App>',
160176
options: [{ allow: 'single-child' }],
@@ -184,6 +200,38 @@ ruleTester.run('jsx-one-expression-per-line', rule, {
184200
`,
185201
features: ['fragment', 'no-ts-old'], // TODO: FIXME: remove no-ts-old and fix
186202
},
203+
{
204+
code: '<App>Hello {name}</App>',
205+
options: [{ allow: 'non-jsx' }],
206+
},
207+
{
208+
code: `
209+
<App>
210+
Hello {name} there!
211+
</App>`,
212+
options: [{ allow: 'non-jsx' }],
213+
},
214+
{
215+
code: `
216+
<App>
217+
Hello {<Bar />} there!
218+
</App>`,
219+
options: [{ allow: 'non-jsx' }],
220+
},
221+
{
222+
code: `
223+
<App>
224+
Hello {(<Bar />)} there!
225+
</App>`,
226+
options: [{ allow: 'non-jsx' }],
227+
},
228+
{
229+
code: `
230+
<App>
231+
Hello {(() => <Bar />)()} there!
232+
</App>`,
233+
options: [{ allow: 'non-jsx' }],
234+
},
187235
]),
188236

189237
invalid: parsers.all([
@@ -493,6 +541,28 @@ foo
493541
],
494542
parserOptions,
495543
},
544+
{
545+
code: `
546+
<Text style={styles.foo}>
547+
<Bar /> <Baz />
548+
</Text>
549+
`,
550+
output: `
551+
<Text style={styles.foo}>
552+
<Bar />${' '/* intentional trailing space */}
553+
{' '}
554+
<Baz />
555+
</Text>
556+
`,
557+
errors: [
558+
{
559+
messageId: 'moveToNewLine',
560+
data: { descriptor: 'Baz' },
561+
},
562+
],
563+
options: [{ allow: 'non-jsx' }],
564+
parserOptions,
565+
},
496566
{
497567
code: `
498568
<Text style={styles.foo}>
@@ -1257,6 +1327,23 @@ foo
12571327
},
12581328
],
12591329
},
1330+
{
1331+
code: `
1332+
<App><Foo /></App>
1333+
`,
1334+
output: `
1335+
<App>
1336+
<Foo />
1337+
</App>
1338+
`,
1339+
options: [{ allow: 'non-jsx' }],
1340+
errors: [
1341+
{
1342+
messageId: 'moveToNewLine',
1343+
data: { descriptor: 'Foo' },
1344+
},
1345+
],
1346+
},
12601347
{
12611348
code: `
12621349
<App

0 commit comments

Comments
 (0)