Skip to content

Commit 1eccf7f

Browse files
authored
Merge pull request #1924 from alexzherdev/1775-one-expression-options
[New] Add allow option to jsx-one-expression-per-line
2 parents c1c3d19 + 3567c5b commit 1eccf7f

File tree

3 files changed

+162
-1
lines changed

3 files changed

+162
-1
lines changed

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

+28
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,31 @@ The following patterns are **not** warnings:
102102
<Hello3 />
103103
</App>
104104
```
105+
106+
## Rule Options
107+
108+
```js
109+
...
110+
"react/jsx-one-expression-per-line": [<enabled>, { "allow": "none"|"literal"|"single-child" }]
111+
...
112+
```
113+
114+
### `allow`
115+
116+
Defaults to `none`.
117+
118+
The following pattern is **not** considered a warning when configured as `"literal"`:
119+
120+
```jsx
121+
<App>Hello</App>
122+
```
123+
124+
The following patterns are **not** considered warnings when configured as `"single-child"`:
125+
126+
```jsx
127+
<App>Hello</App>
128+
129+
<App>{"Hello"}</App>
130+
131+
<App><Hello /></App>
132+
```

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

+37-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ const docsUrl = require('../util/docsUrl');
1111
// Rule Definition
1212
// ------------------------------------------------------------------------------
1313

14+
const optionDefaults = {
15+
allow: 'none'
16+
};
17+
1418
module.exports = {
1519
meta: {
1620
docs: {
@@ -20,10 +24,22 @@ module.exports = {
2024
url: docsUrl('jsx-one-expression-per-line')
2125
},
2226
fixable: 'whitespace',
23-
schema: []
27+
schema: [
28+
{
29+
type: 'object',
30+
properties: {
31+
allow: {
32+
enum: ['none', 'literal', 'single-child']
33+
}
34+
},
35+
default: optionDefaults,
36+
additionalProperties: false
37+
}
38+
]
2439
},
2540

2641
create: function (context) {
42+
const options = Object.assign({}, optionDefaults, context.options[0]);
2743
const sourceCode = context.getSourceCode();
2844

2945
function nodeKey (node) {
@@ -44,8 +60,28 @@ module.exports = {
4460

4561
const openingElement = node.openingElement;
4662
const closingElement = node.closingElement;
63+
const openingElementStartLine = openingElement.loc.start.line;
4764
const openingElementEndLine = openingElement.loc.end.line;
4865
const closingElementStartLine = closingElement.loc.start.line;
66+
const closingElementEndLine = closingElement.loc.end.line;
67+
68+
if (children.length === 1) {
69+
const child = children[0];
70+
if (
71+
openingElementStartLine === openingElementEndLine &&
72+
openingElementEndLine === closingElementStartLine &&
73+
closingElementStartLine === closingElementEndLine &&
74+
closingElementEndLine === child.loc.start.line &&
75+
child.loc.start.line === child.loc.end.line
76+
) {
77+
if (
78+
options.allow === 'single-child' ||
79+
options.allow === 'literal' && (child.type === 'Literal' || child.type === 'JSXText')
80+
) {
81+
return;
82+
}
83+
}
84+
}
4985

5086
const childrenGroupedByLine = {};
5187
const fixDetailsByNode = {};

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

+97
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,24 @@ ruleTester.run('jsx-one-expression-per-line', rule, {
8484
'App',
8585
'>'
8686
].join('\n')
87+
}, {
88+
code: '<App>foo</App>',
89+
options: [{allow: 'literal'}]
90+
}, {
91+
code: '<App>123</App>',
92+
options: [{allow: 'literal'}]
93+
}, {
94+
code: '<App>foo</App>',
95+
options: [{allow: 'single-child'}]
96+
}, {
97+
code: '<App>{"foo"}</App>',
98+
options: [{allow: 'single-child'}]
99+
}, {
100+
code: '<App>{foo && <Bar />}</App>',
101+
options: [{allow: 'single-child'}]
102+
}, {
103+
code: '<App><Foo /></App>',
104+
options: [{allow: 'single-child'}]
87105
}],
88106

89107
invalid: [{
@@ -852,5 +870,84 @@ ruleTester.run('jsx-one-expression-per-line', rule, {
852870
{message: '`{ foo}` must be placed on a new line'}
853871
],
854872
parserOptions: parserOptions
873+
}, {
874+
code: '<App><Foo /></App>',
875+
options: [{allow: 'none'}],
876+
output: [
877+
'<App>',
878+
'<Foo />',
879+
'</App>'
880+
].join('\n'),
881+
errors: [{message: '`Foo` must be placed on a new line'}]
882+
}, {
883+
code: '<App>foo</App>',
884+
options: [{allow: 'none'}],
885+
output: [
886+
'<App>',
887+
'foo',
888+
'</App>'
889+
].join('\n'),
890+
errors: [{message: '`foo` must be placed on a new line'}]
891+
}, {
892+
code: '<App>{"foo"}</App>',
893+
options: [{allow: 'none'}],
894+
output: [
895+
'<App>',
896+
'{"foo"}',
897+
'</App>'
898+
].join('\n'),
899+
errors: [{message: '`{"foo"}` must be placed on a new line'}]
900+
}, {
901+
code: [
902+
'<App>foo',
903+
'</App>'
904+
].join('\n'),
905+
options: [{allow: 'literal'}],
906+
output: [
907+
'<App>',
908+
'foo',
909+
'</App>'
910+
].join('\n'),
911+
errors: [{message: '`foo` must be placed on a new line'}]
912+
}, {
913+
code: '<App><Foo /></App>',
914+
options: [{allow: 'literal'}],
915+
output: [
916+
'<App>',
917+
'<Foo />',
918+
'</App>'
919+
].join('\n'),
920+
errors: [{message: '`Foo` must be placed on a new line'}]
921+
}, {
922+
code: [
923+
'<App',
924+
' foo="1"',
925+
' bar="2"',
926+
'>baz</App>'
927+
].join('\n'),
928+
options: [{allow: 'literal'}],
929+
output: [
930+
'<App',
931+
' foo="1"',
932+
' bar="2"',
933+
'>',
934+
'baz',
935+
'</App>'
936+
].join('\n'),
937+
errors: [{message: '`baz` must be placed on a new line'}]
938+
}, {
939+
code: [
940+
'<App>foo',
941+
'bar',
942+
'</App>'
943+
].join('\n'),
944+
options: [{allow: 'literal'}],
945+
output: [
946+
'<App>',
947+
'foo',
948+
'bar',
949+
'</App>'
950+
].join('\n'),
951+
errors: [{message: '`foobar` must be placed on a new line'}]
855952
}]
856953
});

0 commit comments

Comments
 (0)