Skip to content

Commit 2d18878

Browse files
Carluxljharb
authored andcommitted
[New] jsx-max-props-per-line: add single and multi options
1 parent 95a8a4e commit 2d18878

File tree

4 files changed

+256
-16
lines changed

4 files changed

+256
-16
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
77

88
### Added
99
* add [`no-namespace`] rule ([#2640] @yacinehmito @ljharb)
10+
* [`jsx-max-props-per-line`]: add `single` and `multi` options ([#3078] @SIL0RAK)
1011

1112
### Fixed
1213
* [`display-name`]: Get rid of false position on component detection ([#2759] @iiison)
1314

1415
### Changed
1516
* [`no-access-state-in-setstate`]: passing test for “don't error if it's not a React Component” ([#1873] @kentcdodds)
1617

18+
[#3078]: https://github.com/yannickcr/eslint-plugin-react/pull/3078
1719
[#2640]: https://github.com/yannickcr/eslint-plugin-react/pull/2640
1820
[#2759]: https://github.com/yannickcr/eslint-plugin-react/pull/2759
1921
[#1873]: https://github.com/yannickcr/eslint-plugin-react/pull/1873

docs/rules/jsx-max-props-per-line.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@ Examples of **correct** code for this rule:
3939
...
4040
"react/jsx-max-props-per-line": [<enabled>, { "maximum": <number>, "when": <string> }]
4141
...
42+
43+
// OR
44+
45+
...
46+
"react/jsx-max-props-per-line": [<enabled>, { "maximum": { single <number> multi: <number> } }]
47+
...
4248
```
4349

4450
### `maximum`
@@ -62,8 +68,12 @@ Examples of **correct** code for this rule:
6268
/>;
6369
```
6470

71+
Maximum can be specified as object `{ single: 1, multi: 1 }` to specify maximum allowed number of props for single line and multiple line tags.
72+
6573
### `when`
6674

75+
_when only applied if `maximum` is specified as number._
76+
6777
Possible values:
6878
- `always` (default) - Always check for max props per line.
6979
- `multiline` - Only check for max props per line when jsx tag spans multiple lines.

lib/rules/jsx-max-props-per-line.js

Lines changed: 64 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,24 +29,61 @@ module.exports = {
2929
messages,
3030

3131
schema: [{
32-
type: 'object',
33-
properties: {
34-
maximum: {
35-
type: 'integer',
36-
minimum: 1
32+
anyOf: [{
33+
type: 'object',
34+
properties: {
35+
maximum: {
36+
oneOf: [{
37+
type: 'integer',
38+
minimum: 1
39+
}, {
40+
type: 'object',
41+
properties: {
42+
single: {
43+
type: 'integer',
44+
minimum: 1
45+
},
46+
multi: {
47+
type: 'integer',
48+
minimum: 1
49+
}
50+
}
51+
}]
52+
}
3753
},
38-
when: {
39-
type: 'string',
40-
enum: ['always', 'multiline']
54+
additionalProperties: false
55+
}, {
56+
type: 'object',
57+
properties: {
58+
maximum: {
59+
type: 'number',
60+
minimum: 1
61+
},
62+
when: {
63+
type: 'string',
64+
enum: ['always', 'multiline']
65+
}
4166
}
42-
}
67+
}]
4368
}]
4469
},
4570

4671
create(context) {
4772
const configuration = context.options[0] || {};
4873
const maximum = configuration.maximum || 1;
49-
const when = configuration.when || 'always';
74+
let maximumSingle = null;
75+
let maximumMulti = null;
76+
77+
const isExtendedConfig = typeof maximum !== 'number';
78+
79+
if (isExtendedConfig) {
80+
maximumSingle = maximum.single || Infinity;
81+
maximumMulti = maximum.multi || Infinity;
82+
}
83+
84+
const when = isExtendedConfig
85+
? 'always'
86+
: configuration.when || 'always';
5087

5188
function getPropName(propNode) {
5289
if (propNode.type === 'JSXSpreadAttribute') {
@@ -60,6 +97,7 @@ module.exports = {
6097
const output = [];
6198
const front = line[0].range[0];
6299
const back = line[line.length - 1].range[1];
100+
63101
for (let i = 0; i < line.length; i += max) {
64102
const nodes = line.slice(i, i + max);
65103
output.push(nodes.reduce((prev, curr) => {
@@ -69,7 +107,9 @@ module.exports = {
69107
return `${prev} ${sourceCode.getText(curr)}`;
70108
}, ''));
71109
}
110+
72111
const code = output.join('\n');
112+
73113
return function fix(fixer) {
74114
return fixer.replaceTextRange([front, back], code);
75115
};
@@ -81,7 +121,9 @@ module.exports = {
81121
return;
82122
}
83123

84-
if (when === 'multiline' && node.loc.start.line === node.loc.end.line) {
124+
const isSingleLineTag = node.loc.start.line === node.loc.end.line;
125+
126+
if (when === 'multiline' && isSingleLineTag) {
85127
return;
86128
}
87129

@@ -97,15 +139,22 @@ module.exports = {
97139
return decl;
98140
});
99141

142+
let maxPropsCountPerLine = maximum;
143+
100144
linePartitionedProps.forEach((propsInLine) => {
101-
if (propsInLine.length > maximum) {
102-
const name = getPropName(propsInLine[maximum]);
145+
if (isExtendedConfig) {
146+
maxPropsCountPerLine = isSingleLineTag && propsInLine[0].loc.start.line === node.loc.start.line
147+
? maximumSingle
148+
: maximumMulti;
149+
}
150+
if (propsInLine.length > maxPropsCountPerLine) {
151+
const name = getPropName(propsInLine[maxPropsCountPerLine]);
103152
report(context, messages.newLine, 'newLine', {
104-
node: propsInLine[maximum],
153+
node: propsInLine[maxPropsCountPerLine],
105154
data: {
106155
prop: name
107156
},
108-
fix: generateFixFunction(propsInLine, maximum)
157+
fix: generateFixFunction(propsInLine, maxPropsCountPerLine)
109158
});
110159
}
111160
});

tests/lib/rules/jsx-max-props-per-line.js

Lines changed: 180 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,70 @@ ruleTester.run('jsx-max-props-per-line', rule, {
6060
'/>'
6161
].join('\n'),
6262
options: [{maximum: 2}]
63-
}],
63+
}, {
64+
code: [
65+
'<App',
66+
' foo bar',
67+
' baz',
68+
'/>'
69+
].join('\n'),
70+
options: [{maximum: {multi: 2}}]
71+
}, {
72+
code: [
73+
'<App',
74+
' bar',
75+
' baz',
76+
'/>'
77+
].join('\n'),
78+
options: [{maximum: {multi: 2, single: 1}}]
79+
}, {
80+
code: '<App foo baz bar />',
81+
options: [{maximum: {multi: 2, single: 3}}]
82+
}, {
83+
code: '<App {...this.props} bar />',
84+
options: [{maximum: {single: 2}}]
85+
}, {
86+
code: [
87+
'<App',
88+
' foo bar',
89+
' baz bor',
90+
'/>'
91+
].join('\n'),
92+
options: [{maximum: {multi: 2, single: 1}}]
93+
}, {
94+
code: '<App foo baz bar />',
95+
options: [{maximum: {multi: 2}}]
96+
}, {
97+
code: [
98+
'<App',
99+
' foo bar',
100+
' baz bor',
101+
'/>'
102+
].join('\n'),
103+
options: [{maximum: {single: 1}}]
104+
}, {
105+
code: [
106+
'<App foo bar',
107+
' baz bor',
108+
'/>'
109+
].join('\n'),
110+
options: [{maximum: {single: 2, multi: 2}}]
111+
}, {
112+
code: [
113+
'<App foo bar',
114+
' baz bor',
115+
'/>'
116+
].join('\n'),
117+
options: [{maximum: 2}]
118+
}, {
119+
code: [
120+
'<App foo',
121+
' bar',
122+
'/>'
123+
].join('\n'),
124+
options: [{maximum: 1, when: 'multiline'}]
125+
}
126+
],
64127

65128
invalid: [{
66129
code: '<App foo bar baz />;',
@@ -266,5 +329,121 @@ ruleTester.run('jsx-max-props-per-line', rule, {
266329
messageId: 'newLine',
267330
data: {prop: 'baz'}
268331
}]
332+
},
333+
{
334+
code: '<App foo bar baz />',
335+
output: [
336+
'<App foo',
337+
'bar',
338+
'baz />'
339+
].join('\n'),
340+
options: [{maximum: {single: 1, multi: 1}}],
341+
errors: [{
342+
messageId: 'newLine',
343+
data: {prop: 'bar'}
344+
}]
345+
}, {
346+
code: [
347+
'<App',
348+
' foo bar baz',
349+
'/>'
350+
].join('\n'),
351+
output: [
352+
'<App',
353+
' foo',
354+
'bar',
355+
'baz',
356+
'/>'
357+
].join('\n'),
358+
options: [{maximum: {single: 1, multi: 1}}],
359+
errors: [{
360+
messageId: 'newLine',
361+
data: {prop: 'bar'}
362+
}]
363+
}, {
364+
code: [
365+
'<App foo',
366+
' bar baz',
367+
'/>'
368+
].join('\n'),
369+
output: [
370+
'<App foo',
371+
' bar',
372+
'baz',
373+
'/>'
374+
].join('\n'),
375+
options: [{maximum: {single: 1, multi: 1}}],
376+
errors: [{
377+
messageId: 'newLine',
378+
data: {prop: 'baz'}
379+
}]
380+
}, {
381+
code: [
382+
'<App foo bar',
383+
' bar baz bor',
384+
'/>'
385+
].join('\n'),
386+
output: [
387+
'<App foo bar',
388+
' bar baz',
389+
'bor',
390+
'/>'
391+
].join('\n'),
392+
options: [{maximum: {single: 1, multi: 2}}],
393+
errors: [
394+
{
395+
messageId: 'newLine',
396+
data: {prop: 'bor'}
397+
}]
398+
}, {
399+
code: '<App foo bar baz bor />',
400+
output: [
401+
'<App foo bar baz',
402+
'bor />'
403+
].join('\n'),
404+
options: [{maximum: {single: 3, multi: 2}}],
405+
errors: [
406+
{
407+
messageId: 'newLine',
408+
data: {prop: 'bor'}
409+
}]
410+
}, {
411+
code: [
412+
'<App',
413+
' foo={{',
414+
' }} bar baz bor',
415+
'/>'
416+
].join('\n'),
417+
output: [
418+
'<App',
419+
' foo={{',
420+
' }} bar',
421+
'baz bor',
422+
'/>'
423+
].join('\n'),
424+
options: [{maximum: {multi: 2}}],
425+
errors: [{
426+
messageId: 'newLine',
427+
data: {prop: 'baz'}
428+
}]
429+
}, {
430+
code: [
431+
'<App boz fuz',
432+
' foo={{',
433+
' }} bar baz bor',
434+
'/>'
435+
].join('\n'),
436+
output: [
437+
'<App boz fuz',
438+
' foo={{',
439+
' }} bar',
440+
'baz bor',
441+
'/>'
442+
].join('\n'),
443+
options: [{maximum: {multi: 2, single: 1}}],
444+
errors: [{
445+
messageId: 'newLine',
446+
data: {prop: 'baz'}
447+
}]
269448
}]
270449
});

0 commit comments

Comments
 (0)